Commit e8b780a3 authored by Jose Ivan Vargas's avatar Jose Ivan Vargas

Merge branch 'feature-flags-migrate-to-gl-tabs' into 'master'

Migrate to GlTabs for Feature Flags Page

See merge request gitlab-org/gitlab!42371
parents 380d09a8 ca76d547
......@@ -42,10 +42,6 @@ export default {
},
props: {
helpPath: {
type: String,
required: true,
},
helpClientLibrariesPath: {
type: String,
required: true,
......@@ -80,7 +76,7 @@ export default {
required: true,
},
},
inject: ['projectName'],
inject: ['projectName', 'featureFlagsHelpPagePath'],
data() {
return {
enteredProjectName: '',
......@@ -149,7 +145,9 @@ export default {
</gl-link>
</template>
<template #docsLink="{ content }">
<gl-link :href="helpPath" target="_blank" data-testid="help-link">{{ content }}</gl-link>
<gl-link :href="featureFlagsHelpPagePath" target="_blank" data-testid="help-link">{{
content
}}</gl-link>
</template>
</gl-sprintf>
</p>
......
<script>
import { createNamespacedHelpers } from 'vuex';
import { isEmpty } from 'lodash';
import {
GlAlert,
GlButton,
GlEmptyState,
GlLoadingIcon,
GlModalDirective,
GlLink,
} from '@gitlab/ui';
import { GlButton, GlModalDirective, GlTabs } from '@gitlab/ui';
import { FEATURE_FLAG_SCOPE, USER_LIST_SCOPE } from '../constants';
import FeatureFlagsTab from './feature_flags_tab.vue';
import FeatureFlagsTable from './feature_flags_table.vue';
import UserListsTable from './user_lists_table.vue';
import store from '../store';
import { __, s__ } from '~/locale';
import NavigationTabs from '~/vue_shared/components/navigation_tabs.vue';
import { s__ } from '~/locale';
import TablePagination from '~/vue_shared/components/pagination/table_pagination.vue';
import {
getParameterByName,
......@@ -26,18 +19,17 @@ import ConfigureFeatureFlagsModal from './configure_feature_flags_modal.vue';
const { mapState, mapActions } = createNamespacedHelpers('index');
const SCOPES = { FEATURE_FLAG_SCOPE, USER_LIST_SCOPE };
export default {
store,
components: {
FeatureFlagsTable,
UserListsTable,
NavigationTabs,
TablePagination,
GlAlert,
GlButton,
GlEmptyState,
GlLoadingIcon,
GlLink,
GlTabs,
FeatureFlagsTab,
ConfigureFeatureFlagsModal,
},
directives: {
......@@ -56,14 +48,6 @@ export default {
type: String,
required: true,
},
errorStateSvgPath: {
type: String,
required: true,
},
featureFlagsHelpPagePath: {
type: String,
required: true,
},
featureFlagsClientLibrariesHelpPagePath: {
type: String,
required: true,
......@@ -101,16 +85,14 @@ export default {
},
},
data() {
const scope = getParameterByName('scope') || SCOPES.FEATURE_FLAG_SCOPE;
return {
scope: getParameterByName('scope') || this.$options.scopes.featureFlags,
scope,
page: getParameterByName('page') || '1',
isUserListAlertDismissed: false,
selectedTab: Object.values(SCOPES).indexOf(scope),
};
},
scopes: {
[FEATURE_FLAG_SCOPE]: FEATURE_FLAG_SCOPE,
[USER_LIST_SCOPE]: USER_LIST_SCOPE,
},
computed: {
...mapState([
FEATURE_FLAG_SCOPE,
......@@ -125,31 +107,8 @@ export default {
'isRotating',
'hasRotateError',
]),
secondaryButtonClasses() {
return ['gl-mb-3', 'gl-lg-mr-3', 'gl-lg-mb-0'];
},
navigationControlsClasses() {
return [
'gl-display-flex',
'gl-flex-direction-column',
'gl-mt-3',
'gl-lg-flex-direction-row',
'gl-lg-flex-fill-1',
'gl-lg-justify-content-end',
'gl-lg-mt-0',
];
},
topAreaBaseClasses() {
return [
'gl-border-1',
'gl-border-b-gray-100',
'gl-border-b-solid',
'gl-flex-wrap',
'gl-display-flex',
'gl-flex-direction-column-reverse',
'gl-lg-align-items-center',
'gl-lg-flex-direction-row',
];
return ['gl-display-flex', 'gl-flex-direction-column'];
},
canUserRotateToken() {
return this.rotateInstanceIdPath !== '';
......@@ -171,29 +130,17 @@ export default {
shouldRenderErrorState() {
return this.hasError && !this.isLoading;
},
tabs() {
const { scopes } = this.$options;
return [
{
name: __('Feature Flags'),
scope: scopes[FEATURE_FLAG_SCOPE],
count: this.count[FEATURE_FLAG_SCOPE],
isActive: this.scope === scopes[FEATURE_FLAG_SCOPE],
},
{
name: __('Lists'),
scope: scopes[USER_LIST_SCOPE],
count: this.count[USER_LIST_SCOPE],
isActive: this.scope === scopes[USER_LIST_SCOPE],
shouldRenderFeatureFlags() {
return this.shouldRenderTable(SCOPES.FEATURE_FLAG_SCOPE);
},
];
shouldRenderUserLists() {
return this.shouldRenderTable(SCOPES.USER_LIST_SCOPE);
},
hasNewPath() {
return !isEmpty(this.newFeatureFlagPath);
},
emptyStateTitle() {
return s__(`FeatureFlags|Get started with feature flags`);
return s__('FeatureFlags|Get started with feature flags');
},
},
created() {
......@@ -226,6 +173,12 @@ export default {
page: '1',
});
},
onFeatureFlagsTab() {
this.onChangeTab(SCOPES.FEATURE_FLAG_SCOPE);
},
onUserListsTab() {
this.onChangeTab(SCOPES.USER_LIST_SCOPE);
},
onChangePage(page) {
this.updateFeatureFlagOptions({
scope: this.scope,
......@@ -243,7 +196,7 @@ export default {
historyPushState(buildUrlWithCurrentLocation(`?${queryString}`));
this.setFeatureFlagsOptions(parameters);
if (this.scope === this.$options.scopes.featureFlags) {
if (this.scope === SCOPES.FEATURE_FLAG_SCOPE) {
this.fetchFeatureFlags();
} else {
this.fetchUserLists();
......@@ -267,7 +220,6 @@ export default {
<div>
<configure-feature-flags-modal
v-if="canUserConfigure"
:help-path="featureFlagsHelpPagePath"
:help-client-libraries-path="featureFlagsClientLibrariesHelpPagePath"
:help-client-example-path="featureFlagsClientExampleHelpPagePath"
:api-url="unleashApiUrl"
......@@ -279,13 +231,7 @@ export default {
@token="rotateInstanceId()"
/>
<div :class="topAreaBaseClasses">
<navigation-tabs
:tabs="tabs"
scope="featureflags"
class="gl-border-none!"
@onChangeTab="onChangeTab"
/>
<div :class="navigationControlsClasses">
<div class="gl-display-flex gl-flex-direction-column gl-display-md-none!">
<gl-button
v-if="canUserConfigure"
v-gl-modal="'configure-feature-flags'"
......@@ -293,7 +239,7 @@ export default {
category="secondary"
data-qa-selector="configure_feature_flags_button"
data-testid="ff-configure-button"
:class="secondaryButtonClasses"
class="gl-mb-3"
>
{{ s__('FeatureFlags|Configure') }}
</gl-button>
......@@ -303,10 +249,10 @@ export default {
:href="newUserListPath"
variant="success"
category="secondary"
:class="secondaryButtonClasses"
class="gl-mb-3"
data-testid="ff-new-list-button"
>
{{ s__('FeatureFlags|New list') }}
{{ s__('FeatureFlags|New user list') }}
</gl-button>
<gl-button
......@@ -318,62 +264,87 @@ export default {
{{ s__('FeatureFlags|New feature flag') }}
</gl-button>
</div>
</div>
<gl-alert
v-for="(message, index) in alerts"
:key="index"
data-testid="serverErrors"
variant="danger"
@dismiss="clearAlert(index)"
<gl-tabs v-model="selectedTab" class="gl-align-items-center gl-w-full">
<feature-flags-tab
:title="s__('FeatureFlags|Feature Flags')"
:count="count.featureFlags"
:alerts="alerts"
:is-loading="isLoading"
:loading-label="s__('FeatureFlags|Loading feature flags')"
:error-state="shouldRenderErrorState"
:error-title="s__(`FeatureFlags|There was an error fetching the feature flags.`)"
:empty-state="shouldShowEmptyState"
:empty-title="emptyStateTitle"
data-testid="feature-flags-tab"
@dismissAlert="clearAlert"
@changeTab="onFeatureFlagsTab"
>
{{ message }}
</gl-alert>
<gl-loading-icon
v-if="isLoading"
:label="s__('FeatureFlags|Loading feature flags')"
size="md"
class="js-loading-state prepend-top-20"
/>
<gl-empty-state
v-else-if="shouldRenderErrorState"
:title="s__(`FeatureFlags|There was an error fetching the feature flags.`)"
:description="s__(`FeatureFlags|Try again in a few moments or contact your support team.`)"
:svg-path="errorStateSvgPath"
/>
<gl-empty-state
v-else-if="shouldShowEmptyState"
class="js-feature-flags-empty-state"
:title="emptyStateTitle"
:svg-path="errorStateSvgPath"
>
<template #description>
{{
s__(
'FeatureFlags|Feature flags allow you to configure your code into different flavors by dynamically toggling certain functionality.',
)
}}
<gl-link :href="featureFlagsHelpPagePath" target="_blank" rel="noopener noreferrer">
{{ s__('FeatureFlags|More information') }}
</gl-link>
</template>
</gl-empty-state>
<feature-flags-table
v-else-if="shouldRenderTable($options.scopes.featureFlags)"
v-if="shouldRenderFeatureFlags"
:csrf-token="csrfToken"
:feature-flags="featureFlags"
@toggle-flag="toggleFeatureFlag"
/>
</feature-flags-tab>
<feature-flags-tab
:title="s__('FeatureFlags|User Lists')"
:count="count.userLists"
:alerts="alerts"
:is-loading="isLoading"
:loading-label="s__('FeatureFlags|Loading user lists')"
:error-state="shouldRenderErrorState"
:error-title="s__(`FeatureFlags|There was an error fetching the user lists.`)"
:empty-state="shouldShowEmptyState"
:empty-title="emptyStateTitle"
data-testid="user-lists-tab"
@dismissAlert="clearAlert"
@changeTab="onUserListsTab"
>
<user-lists-table
v-else-if="shouldRenderTable($options.scopes.userLists)"
v-if="shouldRenderUserLists"
:user-lists="userLists"
@delete="deleteUserList"
/>
</feature-flags-tab>
<template #tabs-end>
<div
class="gl-display-none gl-display-md-flex gl-align-items-center gl-flex-fill-1 gl-justify-content-end"
>
<gl-button
v-if="canUserConfigure"
v-gl-modal="'configure-feature-flags'"
variant="info"
category="secondary"
data-qa-selector="configure_feature_flags_button"
data-testid="ff-configure-button"
class="gl-mb-0 gl-mr-4"
>
{{ s__('FeatureFlags|Configure') }}
</gl-button>
<gl-button
v-if="newUserListPath"
:href="newUserListPath"
variant="success"
category="secondary"
class="gl-mb-0 gl-mr-4"
data-testid="ff-new-list-button"
>
{{ s__('FeatureFlags|New user list') }}
</gl-button>
<gl-button
v-if="hasNewPath"
:href="newFeatureFlagPath"
variant="success"
data-testid="ff-new-button"
>
{{ s__('FeatureFlags|New feature flag') }}
</gl-button>
</div>
</template>
</gl-tabs>
</div>
<table-pagination
v-if="shouldRenderPagination"
:change="onChangePage"
......
<script>
import { GlAlert, GlBadge, GlEmptyState, GlLink, GlLoadingIcon, GlTab } from '@gitlab/ui';
export default {
components: { GlAlert, GlBadge, GlEmptyState, GlLink, GlLoadingIcon, GlTab },
props: {
title: {
required: true,
type: String,
},
count: {
required: false,
type: Number,
default: null,
},
alerts: {
required: true,
type: Array,
},
isLoading: {
required: true,
type: Boolean,
},
loadingLabel: {
required: true,
type: String,
},
errorState: {
required: true,
type: Boolean,
},
errorTitle: {
required: true,
type: String,
},
emptyState: {
required: true,
type: Boolean,
},
emptyTitle: {
required: true,
type: String,
},
},
inject: ['errorStateSvgPath', 'featureFlagsHelpPagePath'],
computed: {
itemCount() {
return this.count ?? 0;
},
},
methods: {
clearAlert(index) {
this.$emit('dismissAlert', index);
},
onClick(event) {
return this.$emit('changeTab', event);
},
},
};
</script>
<template>
<gl-tab @click="onClick">
<template #title>
<span data-testid="feature-flags-tab-title">{{ title }}</span>
<gl-badge size="sm" class="gl-tab-counter-badge">{{ itemCount }}</gl-badge>
</template>
<template>
<gl-alert
v-for="(message, index) in alerts"
:key="index"
data-testid="serverErrors"
variant="danger"
@dismiss="clearAlert(index)"
>
{{ message }}
</gl-alert>
<gl-loading-icon v-if="isLoading" :label="loadingLabel" size="md" class="gl-mt-4" />
<gl-empty-state
v-else-if="errorState"
:title="errorTitle"
:description="s__(`FeatureFlags|Try again in a few moments or contact your support team.`)"
:svg-path="errorStateSvgPath"
data-testid="error-state"
/>
<gl-empty-state
v-else-if="emptyState"
:title="emptyTitle"
:svg-path="errorStateSvgPath"
data-testid="empty-state"
>
<template #description>
{{
s__(
'FeatureFlags|Feature flags allow you to configure your code into different flavors by dynamically toggling certain functionality.',
)
}}
<gl-link :href="featureFlagsHelpPagePath" target="_blank">
{{ s__('FeatureFlags|More information') }}
</gl-link>
</template>
</gl-empty-state>
<slot> </slot>
</template>
</gl-tab>
</template>
......@@ -16,6 +16,8 @@ export default () =>
provide() {
return {
projectName: this.dataset.projectName,
featureFlagsHelpPagePath: this.dataset.featureFlagsHelpPagePath,
errorStateSvgPath: this.dataset.errorStateSvgPath,
};
},
render(createElement) {
......@@ -23,8 +25,6 @@ export default () =>
props: {
endpoint: this.dataset.endpoint,
projectId: this.dataset.projectId,
errorStateSvgPath: this.dataset.errorStateSvgPath,
featureFlagsHelpPagePath: this.dataset.featureFlagsHelpPagePath,
featureFlagsClientLibrariesHelpPagePath: this.dataset
.featureFlagsClientLibrariesHelpPagePath,
featureFlagsClientExampleHelpPagePath: this.dataset.featureFlagsClientExampleHelpPagePath,
......
......@@ -5,10 +5,12 @@ import Callout from '~/vue_shared/components/callout.vue';
describe('Configure Feature Flags Modal', () => {
const mockEvent = { preventDefault: jest.fn() };
const projectName = 'fakeProjectName';
const provide = {
projectName: 'fakeProjectName',
featureFlagsHelpPagePath: '/help/path',
};
const propsData = {
helpPath: '/help/path',
helpClientLibrariesPath: '/help/path/#flags',
helpClientExamplePath: '/feature-flags#clientexample',
apiUrl: '/api/url',
......@@ -21,9 +23,7 @@ describe('Configure Feature Flags Modal', () => {
let wrapper;
const factory = (props = {}, { mountFn = shallowMount, ...options } = {}) => {
wrapper = mountFn(Component, {
provide: {
projectName,
},
provide,
stubs: { GlSprintf },
propsData: {
...propsData,
......@@ -61,7 +61,7 @@ describe('Configure Feature Flags Modal', () => {
});
it('should clear the project name input after generating the token', async () => {
findProjectNameInput().vm.$emit('input', projectName);
findProjectNameInput().vm.$emit('input', provide.projectName);
findGlModal().vm.$emit('primary', mockEvent);
await wrapper.vm.$nextTick();
expect(findProjectNameInput().attributes('value')).toBe('');
......@@ -78,7 +78,9 @@ describe('Configure Feature Flags Modal', () => {
});
it('should have links to the documentation', () => {
expect(wrapper.find('[data-testid="help-link"]').attributes('href')).toBe(propsData.helpPath);
expect(wrapper.find('[data-testid="help-link"]').attributes('href')).toBe(
provide.featureFlagsHelpPagePath,
);
expect(wrapper.find('[data-testid="help-client-link"]').attributes('href')).toBe(
propsData.helpClientLibrariesPath,
);
......@@ -91,7 +93,9 @@ describe('Configure Feature Flags Modal', () => {
});
it('should display a message asking to fill the project name', () => {
expect(wrapper.find('[data-testid="prevent-accident-text"]').text()).toMatch(projectName);
expect(wrapper.find('[data-testid="prevent-accident-text"]').text()).toMatch(
provide.projectName,
);
});
it('should display the api URL in an input box', () => {
......@@ -110,7 +114,7 @@ describe('Configure Feature Flags Modal', () => {
beforeEach(factory);
it('should enable the primary action', async () => {
findProjectNameInput().vm.$emit('input', projectName);
findProjectNameInput().vm.$emit('input', provide.projectName);
await wrapper.vm.$nextTick();
const [{ disabled }] = findPrimaryAction().attributes;
expect(disabled).toBe(false);
......
......@@ -2,14 +2,14 @@ import { shallowMount } from '@vue/test-utils';
import MockAdapter from 'axios-mock-adapter';
import { GlEmptyState, GlLoadingIcon } from '@gitlab/ui';
import Api from 'ee/api';
import store from 'ee/feature_flags/store';
import { createStore } from 'ee/feature_flags/store';
import FeatureFlagsTab from 'ee/feature_flags/components/feature_flags_tab.vue';
import FeatureFlagsComponent from 'ee/feature_flags/components/feature_flags.vue';
import FeatureFlagsTable from 'ee/feature_flags/components/feature_flags_table.vue';
import UserListsTable from 'ee/feature_flags/components/user_lists_table.vue';
import ConfigureFeatureFlagsModal from 'ee/feature_flags/components/configure_feature_flags_modal.vue';
import { FEATURE_FLAG_SCOPE, USER_LIST_SCOPE } from 'ee/feature_flags/constants';
import { TEST_HOST } from 'spec/test_constants';
import NavigationTabs from '~/vue_shared/components/navigation_tabs.vue';
import TablePagination from '~/vue_shared/components/pagination/table_pagination.vue';
import axios from '~/lib/utils/axios_utils';
import { getRequestData, userList } from '../mock_data';
......@@ -18,8 +18,6 @@ describe('Feature flags', () => {
const mockData = {
endpoint: `${TEST_HOST}/endpoint.json`,
csrfToken: 'testToken',
errorStateSvgPath: '/assets/illustrations/feature_flag.svg',
featureFlagsHelpPagePath: '/help/feature-flags',
featureFlagsClientLibrariesHelpPagePath: '/help/feature-flags#unleash-clients',
featureFlagsClientExampleHelpPagePath: '/help/feature-flags#client-example',
unleashApiUrl: `${TEST_HOST}/api/unleash`,
......@@ -33,12 +31,20 @@ describe('Feature flags', () => {
let wrapper;
let mock;
let store;
const factory = (propsData = mockData, fn = shallowMount) => {
store = createStore();
wrapper = fn(FeatureFlagsComponent, {
store,
propsData,
provide: {
projectName: 'fakeProjectName',
errorStateSvgPath: '/assets/illustrations/feature_flag.svg',
featureFlagsHelpPagePath: '/help/feature-flags',
},
stubs: {
FeatureFlagsTab,
},
});
};
......@@ -49,7 +55,6 @@ describe('Feature flags', () => {
beforeEach(() => {
mock = new MockAdapter(axios);
jest.spyOn(store, 'dispatch');
jest.spyOn(Api, 'fetchFeatureFlagUserLists').mockResolvedValue({
data: [userList],
headers: {
......@@ -66,6 +71,7 @@ describe('Feature flags', () => {
afterEach(() => {
mock.restore();
wrapper.destroy();
wrapper = null;
});
describe('without permissions', () => {
......@@ -127,10 +133,8 @@ describe('Feature flags', () => {
describe('without feature flags', () => {
let emptyState;
beforeEach(done => {
mock
.onGet(mockData.endpoint, { params: { scope: FEATURE_FLAG_SCOPE, page: '1' } })
.replyOnce(
beforeEach(async () => {
mock.onGet(mockData.endpoint, { params: { scope: FEATURE_FLAG_SCOPE, page: '1' } }).reply(
200,
{
feature_flags: [],
......@@ -144,15 +148,15 @@ describe('Feature flags', () => {
);
factory();
await wrapper.vm.$nextTick();
setImmediate(() => {
emptyState = wrapper.find(GlEmptyState);
done();
});
});
it('should render the empty state', () => {
expect(wrapper.find(GlEmptyState).exists()).toBe(true);
it('should render the empty state', async () => {
await axios.waitForAll();
emptyState = wrapper.find(GlEmptyState);
expect(emptyState.exists()).toBe(true);
});
it('renders configure button', () => {
......@@ -189,6 +193,7 @@ describe('Feature flags', () => {
});
factory();
jest.spyOn(store, 'dispatch');
setImmediate(() => {
done();
});
......@@ -246,7 +251,7 @@ describe('Feature flags', () => {
it('should make an API request when using tabs', () => {
jest.spyOn(wrapper.vm, 'updateFeatureFlagOptions');
wrapper.find(NavigationTabs).vm.$emit('onChangeTab', USER_LIST_SCOPE);
wrapper.find('[data-testid="user-lists-tab"]').vm.$emit('changeTab');
expect(wrapper.vm.updateFeatureFlagOptions).toHaveBeenCalledWith({
scope: USER_LIST_SCOPE,
......@@ -265,7 +270,7 @@ describe('Feature flags', () => {
});
});
beforeEach(() => {
wrapper.find(NavigationTabs).vm.$emit('onChangeTab', USER_LIST_SCOPE);
wrapper.find('[data-testid="user-lists-tab"]').vm.$emit('changeTab');
return wrapper.vm.$nextTick();
});
......
import { mount } from '@vue/test-utils';
import { GlAlert, GlBadge, GlEmptyState, GlLink, GlLoadingIcon, GlTabs } from '@gitlab/ui';
import FeatureFlagsTab from 'ee/feature_flags/components/feature_flags_tab.vue';
const DEFAULT_PROPS = {
title: 'test',
count: 5,
alerts: ['an alert', 'another alert'],
isLoading: false,
loadingLabel: 'test loading',
errorState: false,
errorTitle: 'test title',
emptyState: true,
emptyTitle: 'test empty',
};
const DEFAULT_PROVIDE = {
errorStateSvgPath: '/error.svg',
featureFlagsHelpPagePath: '/help/page/path',
};
describe('ee/feature_flags/components/feature_flags_tab.vue', () => {
let wrapper;
const factory = (props = {}) =>
mount(
{
components: {
GlTabs,
FeatureFlagsTab,
},
render(h) {
return h(GlTabs, [
h(FeatureFlagsTab, { props: this.$attrs, on: this.$listeners }, this.$slots.default),
]);
},
},
{
propsData: {
...DEFAULT_PROPS,
...props,
},
provide: DEFAULT_PROVIDE,
slots: {
default: '<p data-testid="test-slot">testing</p>',
},
},
);
afterEach(() => {
if (wrapper?.destroy) {
wrapper.destroy();
}
wrapper = null;
});
describe('alerts', () => {
let alerts;
beforeEach(() => {
wrapper = factory();
alerts = wrapper.findAll(GlAlert);
});
it('should show any alerts', () => {
expect(alerts).toHaveLength(DEFAULT_PROPS.alerts.length);
alerts.wrappers.forEach((alert, i) => expect(alert.text()).toBe(DEFAULT_PROPS.alerts[i]));
});
it('should emit a dismiss event for a dismissed alert', () => {
alerts.at(0).vm.$emit('dismiss');
expect(wrapper.find(FeatureFlagsTab).emitted('dismissAlert')).toEqual([[0]]);
});
});
describe('loading', () => {
beforeEach(() => {
wrapper = factory({ isLoading: true });
});
it('should show a loading icon and nothing else', () => {
expect(wrapper.find(GlLoadingIcon).exists()).toBe(true);
expect(wrapper.findAll(GlEmptyState)).toHaveLength(0);
});
});
describe('error', () => {
let emptyState;
beforeEach(() => {
wrapper = factory({ errorState: true });
emptyState = wrapper.find(GlEmptyState);
});
it('should show an error state if there has been an error', () => {
expect(emptyState.text()).toContain(DEFAULT_PROPS.errorTitle);
expect(emptyState.text()).toContain(
'Try again in a few moments or contact your support team.',
);
expect(emptyState.props('svgPath')).toBe(DEFAULT_PROVIDE.errorStateSvgPath);
});
});
describe('empty', () => {
let emptyState;
let emptyStateLink;
beforeEach(() => {
wrapper = factory({ emptyState: true });
emptyState = wrapper.find(GlEmptyState);
emptyStateLink = emptyState.find(GlLink);
});
it('should show an empty state if it is empty', () => {
expect(emptyState.text()).toContain(DEFAULT_PROPS.emptyTitle);
expect(emptyState.text()).toContain(
'Feature flags allow you to configure your code into different flavors by dynamically toggling certain functionality.',
);
expect(emptyState.props('svgPath')).toBe(DEFAULT_PROVIDE.errorStateSvgPath);
expect(emptyStateLink.attributes('href')).toBe(DEFAULT_PROVIDE.featureFlagsHelpPagePath);
expect(emptyStateLink.text()).toBe('More information');
});
});
describe('slot', () => {
let slot;
beforeEach(async () => {
wrapper = factory();
await wrapper.vm.$nextTick();
slot = wrapper.find('[data-testid="test-slot"]');
});
it('should display the passed slot', () => {
expect(slot.exists()).toBe(true);
expect(slot.text()).toBe('testing');
});
});
describe('count', () => {
it('should display a count if there is one', async () => {
wrapper = factory();
await wrapper.vm.$nextTick();
expect(wrapper.find(GlBadge).text()).toBe(DEFAULT_PROPS.count.toString());
});
it('should display 0 if there is no count', async () => {
wrapper = factory({ count: undefined });
await wrapper.vm.$nextTick();
expect(wrapper.find(GlBadge).text()).toBe('0');
});
});
describe('title', () => {
it('should show the title', async () => {
wrapper = factory();
await wrapper.vm.$nextTick();
expect(wrapper.find('[data-testid="feature-flags-tab-title"]').text()).toBe(
DEFAULT_PROPS.title,
);
});
});
});
......@@ -10966,6 +10966,9 @@ msgstr ""
msgid "FeatureFlags|Loading feature flags"
msgstr ""
msgid "FeatureFlags|Loading user lists"
msgstr ""
msgid "FeatureFlags|More information"
msgstr ""
......@@ -10984,7 +10987,7 @@ msgstr ""
msgid "FeatureFlags|New feature flag"
msgstr ""
msgid "FeatureFlags|New list"
msgid "FeatureFlags|New user list"
msgstr ""
msgid "FeatureFlags|Percent of users"
......@@ -11023,6 +11026,9 @@ msgstr ""
msgid "FeatureFlags|There was an error fetching the feature flags."
msgstr ""
msgid "FeatureFlags|There was an error fetching the user lists."
msgstr ""
msgid "FeatureFlags|There was an error retrieving user lists"
msgstr ""
......@@ -11038,6 +11044,9 @@ msgstr ""
msgid "FeatureFlags|User List"
msgstr ""
msgid "FeatureFlags|User Lists"
msgstr ""
msgid "FeatureFlag|List"
msgstr ""
......@@ -15114,9 +15123,6 @@ msgstr ""
msgid "List your Bitbucket Server repositories"
msgstr ""
msgid "Lists"
msgstr ""
msgid "Live preview"
msgstr ""
......
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