Commit 71bfccc6 authored by Andrew Fontaine's avatar Andrew Fontaine Committed by Kushal Pandya

Port Feature Flags Table Tests to VTU

Using vue test utils instead of raw mounting things for speed.
parent aef85a4f
......@@ -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