Commit 7add986c authored by Kushal Pandya's avatar Kushal Pandya

Merge branch 'port-ff-tests-to-VTU' into 'master'

Port Feature Flag Tests to Vue Test Utils

See merge request gitlab-org/gitlab!21075
parents aef85a4f 71bfccc6
......@@ -140,7 +140,7 @@ export default {
v-if="canUserRotateToken"
v-gl-tooltip.hover
:title="$options.regenerateInstanceIdTooltip"
class="input-group-text js-ff-rotate-token-button"
class="input-group-text"
@click="rotateToken"
>
<icon name="retry" />
......
......@@ -175,7 +175,7 @@ export default {
<input
type="text"
class="js-env-input form-control pl-4"
class="form-control pl-4 js-env-input"
:aria-label="placeholder"
:value="filter"
:placeholder="placeholder"
......
import { shallowMount, createLocalVue } from '@vue/test-utils';
import { GlButton } from '@gitlab/ui';
import component from 'ee/feature_flags/components/configure_feature_flags_modal.vue';
const localVue = createLocalVue();
......@@ -31,8 +32,8 @@ describe('Configure Feature Flags Modal', () => {
describe('rotate token', () => {
it('should emit a `token` event on click', () => {
wrapper.find('.js-ff-rotate-token-button').trigger('click');
expect(wrapper.emitted('token')).not.toBeEmpty();
wrapper.find(GlButton).vm.$emit('click');
expect(wrapper.emitted('token')).toEqual([[]]);
});
it('should display an error if there is a rotate error', () => {
......
......@@ -77,13 +77,12 @@ describe('Edit feature flag form', () => {
});
describe('with error', () => {
it('should render the error', done => {
it('should render the error', () => {
store.dispatch('edit/receiveUpdateFeatureFlagError', { message: ['The name is required'] });
wrapper.vm.$nextTick(() => {
return wrapper.vm.$nextTick(() => {
expect(wrapper.find('.alert-danger').exists()).toEqual(true);
expect(wrapper.find('.alert-danger').text()).toContain('The name is required');
done();
});
});
});
......
import MockAdapter from 'axios-mock-adapter';
import { createLocalVue, mount } from '@vue/test-utils';
import { GlLoadingIcon } from '@gitlab/ui';
import { createLocalVue, shallowMount } from '@vue/test-utils';
import { GlLoadingIcon, GlButton } from '@gitlab/ui';
import EnvironmentsDropdown from 'ee/feature_flags/components/environments_dropdown.vue';
import { TEST_HOST } from 'spec/test_constants';
import axios from '~/lib/utils/axios_utils';
......@@ -12,7 +12,7 @@ describe('Feature flags > Environments dropdown ', () => {
let mock;
const factory = props => {
wrapper = mount(localVue.extend(EnvironmentsDropdown), {
wrapper = shallowMount(EnvironmentsDropdown, {
localVue,
propsData: {
endpoint: `${TEST_HOST}/environments.json'`,
......@@ -78,20 +78,19 @@ describe('Feature flags > Environments dropdown ', () => {
expect(wrapper.vm.showSuggestions).toEqual(true);
});
it('emits even when a suggestion is clicked', () => {
jest.spyOn(wrapper.vm, '$emit');
it('emits event when a suggestion is clicked', () => {
const button = wrapper
.findAll(GlButton)
.filter(b => b.text() === 'production')
.at(0);
button.vm.$emit('click');
wrapper.find('ul button').trigger('click');
expect(wrapper.vm.$emit).toHaveBeenCalledWith('selectEnvironment', 'production');
});
});
expect(wrapper.emitted('selectEnvironment')).toEqual([['production']]);
});
});
describe('on click clear button', () => {
beforeEach(() => {
wrapper.find('.js-clear-search-input').trigger('click');
wrapper.find(GlButton).vm.$emit('click');
});
it('resets filter value', () => {
......@@ -102,6 +101,8 @@ describe('Feature flags > Environments dropdown ', () => {
expect(wrapper.vm.showSuggestions).toEqual(false);
});
});
});
});
describe('on click create button', () => {
beforeEach(done => {
......@@ -115,10 +116,12 @@ describe('Feature flags > Environments dropdown ', () => {
});
it('emits create event', () => {
jest.spyOn(wrapper.vm, '$emit');
wrapper.find('.js-create-button').trigger('click');
wrapper
.findAll(GlButton)
.at(1)
.vm.$emit('click');
expect(wrapper.vm.$emit).toHaveBeenCalledWith('createClicked', 'production');
expect(wrapper.emitted('createClicked')).toEqual([['production']]);
});
});
});
import Vue from 'vue';
import { createLocalVue, shallowMount } from '@vue/test-utils';
import MockAdapter from 'axios-mock-adapter';
import featureFlagsComponent from 'ee/feature_flags/components/feature_flags.vue';
import mountComponent from 'helpers/vue_mount_component_helper';
import { trimText } from 'helpers/text_helper';
import { GlEmptyState, GlLoadingIcon } from '@gitlab/ui';
import FeatureFlagsComponent from 'ee/feature_flags/components/feature_flags.vue';
import FeatureFlagsTable from 'ee/feature_flags/components/feature_flags_table.vue';
import ConfigureFeatureFlagsModal from 'ee/feature_flags/components/configure_feature_flags_modal.vue';
import { TEST_HOST } from 'spec/test_constants';
import NavigationTabs from '~/vue_shared/components/navigation_tabs';
import TablePagination from '~/vue_shared/components/pagination/table_pagination.vue';
import axios from '~/lib/utils/axios_utils';
import { getRequestData } from '../mock_data';
const localVue = createLocalVue();
describe('Feature flags', () => {
const mockData = {
endpoint: `${TEST_HOST}/endpoint.json`,
......@@ -21,23 +26,31 @@ describe('Feature flags', () => {
newFeatureFlagPath: 'feature-flags/new',
};
let FeatureFlagsComponent;
let component;
let wrapper;
let mock;
const factory = (propsData = mockData) => {
wrapper = shallowMount(FeatureFlagsComponent, {
localVue,
propsData,
sync: false,
});
};
const configureButton = () => wrapper.find('.js-ff-configure');
const newButton = () => wrapper.find('.js-ff-new');
beforeEach(() => {
mock = new MockAdapter(axios);
FeatureFlagsComponent = Vue.extend(featureFlagsComponent);
});
afterEach(() => {
mock.restore();
component.$destroy();
wrapper.destroy();
});
describe('without permissions', () => {
const props = {
const propsData = {
endpoint: `${TEST_HOST}/endpoint.json`,
csrfToken: 'testToken',
errorStateSvgPath: '/assets/illustrations/feature_flag.svg',
......@@ -54,7 +67,7 @@ describe('Feature flags', () => {
.onGet(`${TEST_HOST}/endpoint.json`, { params: { scope: 'all', page: '1' } })
.reply(200, getRequestData, {});
component = mountComponent(FeatureFlagsComponent, props);
factory(propsData);
setImmediate(() => {
done();
......@@ -62,11 +75,11 @@ describe('Feature flags', () => {
});
it('does not render configure button', () => {
expect(component.$el.querySelector('.js-ff-configure')).toBeNull();
expect(configureButton().exists()).toBe(false);
});
it('does not render new feature flag button', () => {
expect(component.$el.querySelector('.js-ff-new')).toBeNull();
expect(newButton().exists()).toBe(false);
});
});
......@@ -76,19 +89,19 @@ describe('Feature flags', () => {
.onGet(`${TEST_HOST}/endpoint.json`, { params: { scope: 'all', page: '1' } })
.replyOnce(200, getRequestData, {});
component = mountComponent(FeatureFlagsComponent, mockData);
factory();
const loadingElement = component.$el.querySelector('.js-loading-state');
const loadingElement = wrapper.find(GlLoadingIcon);
expect(loadingElement).not.toBeNull();
expect(loadingElement.querySelector('span').getAttribute('aria-label')).toEqual(
'Loading feature flags',
);
expect(loadingElement.exists()).toBe(true);
expect(loadingElement.props('label')).toEqual('Loading feature flags');
});
});
describe('successful request', () => {
describe('without feature flags', () => {
let emptyState;
beforeEach(done => {
mock.onGet(mockData.endpoint, { params: { scope: 'all', page: '1' } }).replyOnce(
200,
......@@ -103,55 +116,48 @@ describe('Feature flags', () => {
{},
);
component = mountComponent(FeatureFlagsComponent, mockData);
factory();
setImmediate(() => {
emptyState = wrapper.find(GlEmptyState);
done();
});
});
it('should render the empty state', () => {
expect(component.$el.querySelector('.js-feature-flags-empty-state')).not.toBeNull();
expect(wrapper.find(GlEmptyState).exists()).toBe(true);
});
it('renders configure button', () => {
expect(component.$el.querySelector('.js-ff-configure')).not.toBeNull();
expect(configureButton().exists()).toBe(true);
});
it('renders new feature flag button', () => {
expect(component.$el.querySelector('.js-ff-new')).not.toBeNull();
expect(newButton().exists()).toBe(true);
});
describe('in all tab', () => {
it('renders generic title', () => {
expect(
component.$el.querySelector('.js-feature-flags-empty-state h4').textContent.trim(),
).toEqual('Get started with feature flags');
expect(emptyState.props('title')).toEqual('Get started with feature flags');
});
});
describe('in disabled tab', () => {
it('renders disabled title', done => {
component.scope = 'disabled';
it('renders disabled title', () => {
wrapper.setData({ scope: 'disabled' });
Vue.nextTick(() => {
expect(
component.$el.querySelector('.js-feature-flags-empty-state h4').textContent.trim(),
).toEqual('There are no inactive feature flags');
done();
return localVue.nextTick(() => {
expect(emptyState.props('title')).toEqual('There are no inactive feature flags');
});
});
});
describe('in enabled tab', () => {
it('renders enabled title', done => {
component.scope = 'enabled';
it('renders enabled title', () => {
wrapper.setData({ scope: 'enabled' });
Vue.nextTick(() => {
expect(
component.$el.querySelector('.js-feature-flags-empty-state h4').textContent.trim(),
).toEqual('There are no active feature flags');
done();
localVue.nextTick(() => {
expect(emptyState.props('title')).toEqual('There are no active feature flags');
});
});
});
......@@ -170,51 +176,53 @@ describe('Feature flags', () => {
'X-Total-Pages': '5',
});
component = mountComponent(FeatureFlagsComponent, mockData);
factory();
setImmediate(() => {
done();
});
});
it('should render a table with feature flags', () => {
expect(component.$el.querySelectorAll('.js-feature-flag-table')).not.toBeNull();
expect(component.$el.querySelector('.feature-flag-name').textContent.trim()).toEqual(
getRequestData.feature_flags[0].name,
);
expect(component.$el.querySelector('.feature-flag-description').textContent.trim()).toEqual(
getRequestData.feature_flags[0].description,
const table = wrapper.find(FeatureFlagsTable);
expect(wrapper.find(FeatureFlagsTable).exists()).toBe(true);
expect(table.props('featureFlags')).toEqual(
expect.arrayContaining([
expect.objectContaining({
name: getRequestData.feature_flags[0].name,
description: getRequestData.feature_flags[0].description,
}),
]),
);
});
it('renders configure button', () => {
expect(component.$el.querySelector('.js-ff-configure')).not.toBeNull();
expect(configureButton().exists()).toBe(true);
});
it('renders new feature flag button', () => {
expect(component.$el.querySelector('.js-ff-new')).not.toBeNull();
expect(newButton().exists()).toBe(true);
});
describe('pagination', () => {
it('should render pagination', () => {
expect(component.$el.querySelectorAll('.gl-pagination')).not.toBeNull();
expect(wrapper.find(TablePagination).exists()).toBe(true);
});
it('should make an API request when page is clicked', () => {
jest.spyOn(component, 'updateFeatureFlagOptions');
component.$el.querySelector('.gl-pagination li:nth-child(5) .page-link').click();
jest.spyOn(wrapper.vm, 'updateFeatureFlagOptions');
wrapper.find(TablePagination).vm.change(4);
expect(component.updateFeatureFlagOptions).toHaveBeenCalledWith({
expect(wrapper.vm.updateFeatureFlagOptions).toHaveBeenCalledWith({
scope: 'all',
page: '4',
});
});
it('should make an API request when using tabs', () => {
jest.spyOn(component, 'updateFeatureFlagOptions');
component.$el.querySelector('.js-featureflags-tab-enabled').click();
jest.spyOn(wrapper.vm, 'updateFeatureFlagOptions');
wrapper.find(NavigationTabs).vm.$emit('onChangeTab', 'enabled');
expect(component.updateFeatureFlagOptions).toHaveBeenCalledWith({
expect(wrapper.vm.updateFeatureFlagOptions).toHaveBeenCalledWith({
scope: 'enabled',
page: '1',
});
......@@ -227,7 +235,7 @@ describe('Feature flags', () => {
beforeEach(done => {
mock.onGet(mockData.endpoint, { params: { scope: 'all', page: '1' } }).replyOnce(500, {});
component = mountComponent(FeatureFlagsComponent, mockData);
factory();
setImmediate(() => {
done();
......@@ -235,23 +243,28 @@ describe('Feature flags', () => {
});
it('should render error state', () => {
expect(trimText(component.$el.querySelector('.empty-state').textContent)).toContain(
'There was an error fetching the feature flags. Try again in a few moments or contact your support team.',
const emptyState = wrapper.find(GlEmptyState);
expect(emptyState.props('title')).toEqual('There was an error fetching the feature flags.');
expect(emptyState.props('description')).toEqual(
'Try again in a few moments or contact your support team.',
);
});
it('renders configure button', () => {
expect(component.$el.querySelector('.js-ff-configure')).not.toBeNull();
expect(configureButton().exists()).toBe(true);
});
it('renders new feature flag button', () => {
expect(component.$el.querySelector('.js-ff-new')).not.toBeNull();
expect(newButton().exists()).toBe(true);
});
});
describe('rotate instance id', () => {
beforeEach(done => {
component = mountComponent(FeatureFlagsComponent, mockData);
mock
.onGet(`${TEST_HOST}/endpoint.json`, { params: { scope: 'all', page: '1' } })
.reply(200, getRequestData, {});
factory();
setImmediate(() => {
done();
......@@ -259,9 +272,9 @@ describe('Feature flags', () => {
});
it('should fire the rotate action when a `token` event is received', () => {
const actionSpy = jest.spyOn(component, 'rotateInstanceId');
const [modal] = component.$children;
modal.$emit('token');
const actionSpy = jest.spyOn(wrapper.vm, 'rotateInstanceId');
const modal = wrapper.find(ConfigureFeatureFlagsModal);
modal.vm.$emit('token');
expect(actionSpy).toHaveBeenCalled();
});
......
import _ from 'underscore';
import { createLocalVue, mount } from '@vue/test-utils';
import { createLocalVue, shallowMount } from '@vue/test-utils';
import { GlFormTextarea, GlFormCheckbox } from '@gitlab/ui';
import Form from 'ee/feature_flags/components/form.vue';
import EnvironmentsDropdown from 'ee/feature_flags/components/environments_dropdown.vue';
......@@ -33,7 +33,7 @@ describe('feature flag form', () => {
const factory = (props = {}) => {
const localVue = createLocalVue();
wrapper = mount(localVue.extend(Form), {
wrapper = shallowMount(Form, {
localVue,
propsData: props,
provide: {
......@@ -183,7 +183,7 @@ describe('feature flag form', () => {
describe('deleting an existing scope', () => {
beforeEach(() => {
wrapper.find('.js-delete-scope').trigger('click');
wrapper.find('.js-delete-scope').vm.$emit('click');
});
it('should add `shouldBeDestroyed` key the clicked scope', () => {
......@@ -218,7 +218,7 @@ describe('feature flag form', () => {
],
});
wrapper.find('.js-delete-scope').trigger('click');
wrapper.find('.js-delete-scope').vm.$emit('click');
expect(wrapper.vm.formScopes).toEqual([]);
});
......@@ -280,7 +280,7 @@ describe('feature flag form', () => {
.setSelected();
};
beforeEach(done => {
beforeEach(() => {
factory({
...requiredProps,
name: 'feature_flag_1',
......@@ -300,10 +300,10 @@ describe('feature flag form', () => {
],
});
wrapper.vm.$nextTick(done, done.fail);
return wrapper.vm.$nextTick();
});
it('should emit handleSubmit with the updated data', done => {
it('should emit handleSubmit with the updated data', () => {
wrapper.find('#feature-flag-name').setValue('feature_flag_2');
wrapper
......@@ -318,7 +318,7 @@ describe('feature flag form', () => {
wrapper.find(ToggleButton).vm.$emit('change', true);
wrapper.vm
return wrapper.vm
.$nextTick()
.then(() => {
......@@ -333,7 +333,7 @@ describe('feature flag form', () => {
return wrapper.vm.$nextTick();
})
.then(() => {
wrapper.find({ ref: 'submitButton' }).trigger('click');
wrapper.find({ ref: 'submitButton' }).vm.$emit('click');
const data = wrapper.emitted().handleSubmit[0][0];
......@@ -373,9 +373,7 @@ describe('feature flag form', () => {
rolloutUserIds: '',
},
]);
})
.then(done)
.catch(done.fail);
});
});
});
});
......
import Vuex from 'vuex';
import Vue from 'vue';
import { createLocalVue, mount } from '@vue/test-utils';
import { createLocalVue, shallowMount } from '@vue/test-utils';
import Form from 'ee/feature_flags/components/form.vue';
import newModule from 'ee/feature_flags/store/modules/new';
import NewFeatureFlag from 'ee/feature_flags/components/new_feature_flag.vue';
......@@ -19,7 +18,7 @@ describe('New feature flag form', () => {
});
const factory = () => {
wrapper = mount(localVue.extend(NewFeatureFlag), {
wrapper = shallowMount(NewFeatureFlag, {
localVue,
propsData: {
endpoint: 'feature_flags.json',
......@@ -40,12 +39,11 @@ describe('New feature flag form', () => {
});
describe('with error', () => {
it('should render the error', done => {
it('should render the error', () => {
store.dispatch('new/receiveCreateFeatureFlagError', { message: ['The name is required'] });
Vue.nextTick(() => {
return wrapper.vm.$nextTick(() => {
expect(wrapper.find('.alert').exists()).toEqual(true);
expect(wrapper.find('.alert').text()).toContain('The name is required');
done();
});
});
});
......@@ -59,17 +57,16 @@ describe('New feature flag form', () => {
});
it('should render default * row', () => {
expect(wrapper.vm.scopes).toEqual([
{
const defaultScope = {
id: expect.any(String),
environmentScope: '*',
active: true,
rolloutStrategy: ROLLOUT_STRATEGY_ALL_USERS,
rolloutPercentage: DEFAULT_PERCENT_ROLLOUT,
rolloutUserIds: [],
},
]);
};
expect(wrapper.vm.scopes).toEqual([defaultScope]);
expect(wrapper.find('.js-scope-all').exists()).toEqual(true);
expect(wrapper.find(Form).props('scopes')).toContainEqual(defaultScope);
});
});
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