Commit bfeb5f25 authored by Phil Hughes's avatar Phil Hughes

Merge branch 'ps-remove-unused-environments-modules' into 'master'

Clean up unused environments modules

See merge request gitlab-org/gitlab!82647
parents 289e5ac0 734f5b43
<script> <script>
import { GlBadge, GlButton, GlModalDirective, GlTab, GlTabs } from '@gitlab/ui'; import { GlBadge, GlPagination, GlTab, GlTabs } from '@gitlab/ui';
import createFlash from '~/flash'; import { s__, __, sprintf } from '~/locale';
import { s__ } from '~/locale'; import { updateHistory, setUrlParams, queryToObject } from '~/lib/utils/url_utility';
import eventHub from '../event_hub'; import environmentAppQuery from '../graphql/queries/environment_app.query.graphql';
import environmentsMixin from '../mixins/environments_mixin'; import pollIntervalQuery from '../graphql/queries/poll_interval.query.graphql';
import EnvironmentsPaginationApiMixin from '../mixins/environments_pagination_api_mixin'; import pageInfoQuery from '../graphql/queries/page_info.query.graphql';
import ConfirmRollbackModal from './confirm_rollback_modal.vue'; import environmentToDeleteQuery from '../graphql/queries/environment_to_delete.query.graphql';
import DeleteEnvironmentModal from './delete_environment_modal.vue'; import environmentToRollbackQuery from '../graphql/queries/environment_to_rollback.query.graphql';
import emptyState from './empty_state.vue'; import environmentToStopQuery from '../graphql/queries/environment_to_stop.query.graphql';
import environmentToChangeCanaryQuery from '../graphql/queries/environment_to_change_canary.query.graphql';
import { ENVIRONMENTS_SCOPE } from '../constants';
import EnvironmentFolder from './environment_folder.vue';
import EnableReviewAppModal from './enable_review_app_modal.vue'; import EnableReviewAppModal from './enable_review_app_modal.vue';
import StopEnvironmentModal from './stop_environment_modal.vue'; import StopEnvironmentModal from './stop_environment_modal.vue';
import EnvironmentItem from './new_environment_item.vue';
import ConfirmRollbackModal from './confirm_rollback_modal.vue';
import DeleteEnvironmentModal from './delete_environment_modal.vue';
import CanaryUpdateModal from './canary_update_modal.vue';
import EmptyState from './empty_state.vue';
export default { export default {
i18n: {
newEnvironmentButtonLabel: s__('Environments|New environment'),
reviewAppButtonLabel: s__('Environments|Enable review app'),
},
modal: {
id: 'enable-review-app-info',
},
components: { components: {
DeleteEnvironmentModal,
CanaryUpdateModal,
ConfirmRollbackModal, ConfirmRollbackModal,
emptyState, EmptyState,
EnvironmentFolder,
EnableReviewAppModal, EnableReviewAppModal,
EnvironmentItem,
StopEnvironmentModal,
GlBadge, GlBadge,
GlButton, GlPagination,
GlTab, GlTab,
GlTabs, GlTabs,
StopEnvironmentModal,
DeleteEnvironmentModal,
}, },
directives: { apollo: {
'gl-modal': GlModalDirective, environmentApp: {
}, query: environmentAppQuery,
mixins: [EnvironmentsPaginationApiMixin, environmentsMixin], variables() {
props: { return {
endpoint: { scope: this.scope,
type: String, page: this.page ?? 1,
required: true, };
},
pollInterval() {
return this.interval;
},
},
interval: {
query: pollIntervalQuery,
},
pageInfo: {
query: pageInfoQuery,
},
environmentToDelete: {
query: environmentToDeleteQuery,
}, },
canCreateEnvironment: { environmentToRollback: {
type: Boolean, query: environmentToRollbackQuery,
required: true,
}, },
newEnvironmentPath: { environmentToStop: {
type: String, query: environmentToStopQuery,
required: true,
}, },
helpPagePath: { environmentToChangeCanary: {
type: String, query: environmentToChangeCanaryQuery,
required: true, },
weight: {
query: environmentToChangeCanaryQuery,
}, },
}, },
inject: ['newEnvironmentPath', 'canCreateEnvironment', 'helpPagePath'],
created() { i18n: {
eventHub.$on('toggleFolder', this.toggleFolder); newEnvironmentButtonLabel: s__('Environments|New environment'),
eventHub.$on('toggleDeployBoard', this.toggleDeployBoard); reviewAppButtonLabel: s__('Environments|Enable review app'),
available: __('Available'),
stopped: __('Stopped'),
prevPage: __('Go to previous page'),
nextPage: __('Go to next page'),
next: __('Next'),
prev: __('Prev'),
goto: (page) => sprintf(__('Go to page %{page}'), { page }),
}, },
modalId: 'enable-review-app-info',
beforeDestroy() { data() {
// eslint-disable-next-line @gitlab/no-global-event-off const { page = '1', scope } = queryToObject(window.location.search);
eventHub.$off('toggleFolder'); return {
// eslint-disable-next-line @gitlab/no-global-event-off interval: undefined,
eventHub.$off('toggleDeployBoard'); isReviewAppModalVisible: false,
page: parseInt(page, 10),
pageInfo: {},
scope: Object.values(ENVIRONMENTS_SCOPE).includes(scope)
? scope
: ENVIRONMENTS_SCOPE.AVAILABLE,
environmentToDelete: {},
environmentToRollback: {},
environmentToStop: {},
environmentToChangeCanary: {},
weight: 0,
};
}, },
computed: {
methods: { canSetupReviewApp() {
toggleDeployBoard(model) { return this.environmentApp?.reviewApp?.canSetupReviewApp;
this.store.toggleDeployBoard(model.id);
}, },
toggleFolder(folder) { folders() {
this.store.toggleFolder(folder); return this.environmentApp?.environments?.filter((e) => e.size > 1) ?? [];
if (!folder.isOpen) {
this.fetchChildEnvironments(folder, true);
}
}, },
environments() {
fetchChildEnvironments(folder, showLoader = false) { return this.environmentApp?.environments?.filter((e) => e.size === 1) ?? [];
this.store.updateEnvironmentProp(folder, 'isLoadingFolderContent', showLoader);
this.service
.getFolderContent(folder.folder_path, folder.state)
.then((response) => this.store.setfolderContent(folder, response.data.environments))
.then(() => this.store.updateEnvironmentProp(folder, 'isLoadingFolderContent', false))
.catch(() => {
createFlash({
message: s__('Environments|An error occurred while fetching the environments.'),
});
this.store.updateEnvironmentProp(folder, 'isLoadingFolderContent', false);
});
}, },
hasEnvironments() {
return this.environments.length > 0 || this.folders.length > 0;
},
availableCount() {
return this.environmentApp?.availableCount;
},
addEnvironment() {
if (!this.canCreateEnvironment) {
return null;
}
successCallback(resp) { return {
this.saveData(resp); text: this.$options.i18n.newEnvironmentButtonLabel,
attributes: {
// We need to verify if any folder is open to also update it href: this.newEnvironmentPath,
const openFolders = this.store.getOpenFolders(); category: 'primary',
if (openFolders.length) { variant: 'confirm',
openFolders.forEach((folder) => this.fetchChildEnvironments(folder)); },
};
},
openReviewAppModal() {
if (!this.canSetupReviewApp) {
return null;
} }
return {
text: this.$options.i18n.reviewAppButtonLabel,
attributes: {
category: 'secondary',
variant: 'confirm',
},
};
},
stoppedCount() {
return this.environmentApp?.stoppedCount;
}, },
totalItems() {
return this.pageInfo?.total;
},
itemsPerPage() {
return this.pageInfo?.perPage;
},
},
mounted() {
window.addEventListener('popstate', this.syncPageFromQueryParams);
}, },
destroyed() {
window.removeEventListener('popstate', this.syncPageFromQueryParams);
this.$apollo.queries.environmentApp.stopPolling();
},
methods: {
showReviewAppModal() {
this.isReviewAppModalVisible = true;
},
setScope(scope) {
this.scope = scope;
this.moveToPage(1);
},
movePage(direction) {
this.moveToPage(this.pageInfo[`${direction}Page`]);
},
moveToPage(page) {
this.page = page;
updateHistory({
url: setUrlParams({ page: this.page }),
title: document.title,
});
this.resetPolling();
},
syncPageFromQueryParams() {
const { page = '1' } = queryToObject(window.location.search);
this.page = parseInt(page, 10);
},
resetPolling() {
this.$apollo.queries.environmentApp.stopPolling();
this.$apollo.queries.environmentApp.refetch();
this.$nextTick(() => {
if (this.interval) {
this.$apollo.queries.environmentApp.startPolling(this.interval);
}
});
},
},
ENVIRONMENTS_SCOPE,
}; };
</script> </script>
<template> <template>
<div class="environments-section"> <div>
<stop-environment-modal :environment="environmentInStopModal" /> <enable-review-app-modal
<delete-environment-modal :environment="environmentInDeleteModal" /> v-if="canSetupReviewApp"
<confirm-rollback-modal :environment="environmentInRollbackModal" /> v-model="isReviewAppModalVisible"
:modal-id="$options.modalId"
<div class="gl-w-full"> data-testid="enable-review-app-modal"
<div class="gl-display-flex gl-flex-direction-column gl-mt-3 gl-md-display-none!"> />
<gl-button <delete-environment-modal :environment="environmentToDelete" graphql />
v-if="state.reviewAppDetails.can_setup_review_app" <stop-environment-modal :environment="environmentToStop" graphql />
v-gl-modal="$options.modal.id" <confirm-rollback-modal :environment="environmentToRollback" graphql />
data-testid="enable-review-app" <canary-update-modal :environment="environmentToChangeCanary" :weight="weight" />
variant="info" <gl-tabs
category="secondary" :action-secondary="addEnvironment"
type="button" :action-primary="openReviewAppModal"
class="gl-mb-3 gl-flex-grow-1" sync-active-tab-with-query-params
>{{ $options.i18n.reviewAppButtonLabel }}</gl-button query-param-name="scope"
> @primary="showReviewAppModal"
<gl-button >
v-if="canCreateEnvironment" <gl-tab
:href="newEnvironmentPath" :query-param-value="$options.ENVIRONMENTS_SCOPE.AVAILABLE"
data-testid="new-environment" @click="setScope($options.ENVIRONMENTS_SCOPE.AVAILABLE)"
category="primary" >
variant="confirm" <template #title>
>{{ $options.i18n.newEnvironmentButtonLabel }}</gl-button <span>{{ $options.i18n.available }}</span>
> <gl-badge size="sm" class="gl-tab-counter-badge">
</div> {{ availableCount }}
<gl-tabs :value="activeTab" content-class="gl-display-none"> </gl-badge>
<gl-tab
v-for="(tab, idx) in tabs"
:key="idx"
:title-item-class="`js-environments-tab-${tab.scope}`"
@click="onChangeTab(tab.scope)"
>
<template #title>
<span>{{ tab.name }}</span>
<gl-badge size="sm" class="gl-tab-counter-badge">{{ tab.count }}</gl-badge>
</template>
</gl-tab>
<template #tabs-end>
<div
class="gl-display-none gl-md-display-flex gl-lg-align-items-center gl-lg-flex-direction-row gl-lg-flex-fill-1 gl-lg-justify-content-end gl-lg-mt-0"
>
<gl-button
v-if="state.reviewAppDetails.can_setup_review_app"
v-gl-modal="$options.modal.id"
data-testid="enable-review-app"
variant="info"
category="secondary"
type="button"
class="gl-mb-3 gl-lg-mr-3 gl-lg-mb-0"
>{{ $options.i18n.reviewAppButtonLabel }}</gl-button
>
<gl-button
v-if="canCreateEnvironment"
:href="newEnvironmentPath"
data-testid="new-environment"
category="primary"
variant="confirm"
>{{ $options.i18n.newEnvironmentButtonLabel }}</gl-button
>
</div>
</template> </template>
</gl-tabs> </gl-tab>
<container <gl-tab
:is-loading="isLoading" :query-param-value="$options.ENVIRONMENTS_SCOPE.STOPPED"
:environments="state.environments" @click="setScope($options.ENVIRONMENTS_SCOPE.STOPPED)"
:pagination="state.paginationInformation"
@onChangePage="onChangePage"
> >
<template v-if="!isLoading && state.environments.length === 0" #empty-state> <template #title>
<empty-state :help-path="helpPagePath" /> <span>{{ $options.i18n.stopped }}</span>
<gl-badge size="sm" class="gl-tab-counter-badge">
{{ stoppedCount }}
</gl-badge>
</template> </template>
</container> </gl-tab>
<enable-review-app-modal </gl-tabs>
v-if="state.reviewAppDetails.can_setup_review_app" <template v-if="hasEnvironments">
:modal-id="$options.modal.id" <environment-folder
data-testid="enable-review-app-modal" v-for="folder in folders"
:key="folder.name"
class="gl-mb-3"
:scope="scope"
:nested-environment="folder"
/>
<environment-item
v-for="environment in environments"
:key="environment.name"
class="gl-mb-3 gl-border-gray-100 gl-border-1 gl-border-b-solid"
:environment="environment.latest"
@change="resetPolling"
/> />
</div> </template>
<empty-state v-else :help-path="helpPagePath" />
<gl-pagination
align="center"
:total-items="totalItems"
:per-page="itemsPerPage"
:value="page"
:next="$options.i18n.next"
:prev="$options.i18n.prev"
:label-previous-page="$options.prevPage"
:label-next-page="$options.nextPage"
:label-page="$options.goto"
@next="movePage('next')"
@previous="movePage('previous')"
@input="moveToPage"
/>
</div> </div>
</template> </template>
<script>
import { GlBadge, GlPagination, GlTab, GlTabs } from '@gitlab/ui';
import { s__, __, sprintf } from '~/locale';
import { updateHistory, setUrlParams, queryToObject } from '~/lib/utils/url_utility';
import environmentAppQuery from '../graphql/queries/environment_app.query.graphql';
import pollIntervalQuery from '../graphql/queries/poll_interval.query.graphql';
import pageInfoQuery from '../graphql/queries/page_info.query.graphql';
import environmentToDeleteQuery from '../graphql/queries/environment_to_delete.query.graphql';
import environmentToRollbackQuery from '../graphql/queries/environment_to_rollback.query.graphql';
import environmentToStopQuery from '../graphql/queries/environment_to_stop.query.graphql';
import environmentToChangeCanaryQuery from '../graphql/queries/environment_to_change_canary.query.graphql';
import { ENVIRONMENTS_SCOPE } from '../constants';
import EnvironmentFolder from './new_environment_folder.vue';
import EnableReviewAppModal from './enable_review_app_modal.vue';
import StopEnvironmentModal from './stop_environment_modal.vue';
import EnvironmentItem from './new_environment_item.vue';
import ConfirmRollbackModal from './confirm_rollback_modal.vue';
import DeleteEnvironmentModal from './delete_environment_modal.vue';
import CanaryUpdateModal from './canary_update_modal.vue';
import EmptyState from './empty_state.vue';
export default {
components: {
DeleteEnvironmentModal,
CanaryUpdateModal,
ConfirmRollbackModal,
EmptyState,
EnvironmentFolder,
EnableReviewAppModal,
EnvironmentItem,
StopEnvironmentModal,
GlBadge,
GlPagination,
GlTab,
GlTabs,
},
apollo: {
environmentApp: {
query: environmentAppQuery,
variables() {
return {
scope: this.scope,
page: this.page ?? 1,
};
},
pollInterval() {
return this.interval;
},
},
interval: {
query: pollIntervalQuery,
},
pageInfo: {
query: pageInfoQuery,
},
environmentToDelete: {
query: environmentToDeleteQuery,
},
environmentToRollback: {
query: environmentToRollbackQuery,
},
environmentToStop: {
query: environmentToStopQuery,
},
environmentToChangeCanary: {
query: environmentToChangeCanaryQuery,
},
weight: {
query: environmentToChangeCanaryQuery,
},
},
inject: ['newEnvironmentPath', 'canCreateEnvironment', 'helpPagePath'],
i18n: {
newEnvironmentButtonLabel: s__('Environments|New environment'),
reviewAppButtonLabel: s__('Environments|Enable review app'),
available: __('Available'),
stopped: __('Stopped'),
prevPage: __('Go to previous page'),
nextPage: __('Go to next page'),
next: __('Next'),
prev: __('Prev'),
goto: (page) => sprintf(__('Go to page %{page}'), { page }),
},
modalId: 'enable-review-app-info',
data() {
const { page = '1', scope } = queryToObject(window.location.search);
return {
interval: undefined,
isReviewAppModalVisible: false,
page: parseInt(page, 10),
scope: Object.values(ENVIRONMENTS_SCOPE).includes(scope)
? scope
: ENVIRONMENTS_SCOPE.AVAILABLE,
environmentToDelete: {},
environmentToRollback: {},
environmentToStop: {},
environmentToChangeCanary: {},
weight: 0,
};
},
computed: {
canSetupReviewApp() {
return this.environmentApp?.reviewApp?.canSetupReviewApp;
},
folders() {
return this.environmentApp?.environments?.filter((e) => e.size > 1) ?? [];
},
environments() {
return this.environmentApp?.environments?.filter((e) => e.size === 1) ?? [];
},
hasEnvironments() {
return this.environments.length > 0 || this.folders.length > 0;
},
availableCount() {
return this.environmentApp?.availableCount;
},
addEnvironment() {
if (!this.canCreateEnvironment) {
return null;
}
return {
text: this.$options.i18n.newEnvironmentButtonLabel,
attributes: {
href: this.newEnvironmentPath,
category: 'primary',
variant: 'confirm',
},
};
},
openReviewAppModal() {
if (!this.canSetupReviewApp) {
return null;
}
return {
text: this.$options.i18n.reviewAppButtonLabel,
attributes: {
category: 'secondary',
variant: 'confirm',
},
};
},
stoppedCount() {
return this.environmentApp?.stoppedCount;
},
totalItems() {
return this.pageInfo?.total;
},
itemsPerPage() {
return this.pageInfo?.perPage;
},
},
mounted() {
window.addEventListener('popstate', this.syncPageFromQueryParams);
},
destroyed() {
window.removeEventListener('popstate', this.syncPageFromQueryParams);
this.$apollo.queries.environmentApp.stopPolling();
},
methods: {
showReviewAppModal() {
this.isReviewAppModalVisible = true;
},
setScope(scope) {
this.scope = scope;
this.moveToPage(1);
},
movePage(direction) {
this.moveToPage(this.pageInfo[`${direction}Page`]);
},
moveToPage(page) {
this.page = page;
updateHistory({
url: setUrlParams({ page: this.page }),
title: document.title,
});
this.resetPolling();
},
syncPageFromQueryParams() {
const { page = '1' } = queryToObject(window.location.search);
this.page = parseInt(page, 10);
},
resetPolling() {
this.$apollo.queries.environmentApp.stopPolling();
this.$apollo.queries.environmentApp.refetch();
this.$nextTick(() => {
if (this.interval) {
this.$apollo.queries.environmentApp.startPolling(this.interval);
}
});
},
},
ENVIRONMENTS_SCOPE,
};
</script>
<template>
<div>
<enable-review-app-modal
v-if="canSetupReviewApp"
v-model="isReviewAppModalVisible"
:modal-id="$options.modalId"
data-testid="enable-review-app-modal"
/>
<delete-environment-modal :environment="environmentToDelete" graphql />
<stop-environment-modal :environment="environmentToStop" graphql />
<confirm-rollback-modal :environment="environmentToRollback" graphql />
<canary-update-modal :environment="environmentToChangeCanary" :weight="weight" />
<gl-tabs
:action-secondary="addEnvironment"
:action-primary="openReviewAppModal"
sync-active-tab-with-query-params
query-param-name="scope"
@primary="showReviewAppModal"
>
<gl-tab
:query-param-value="$options.ENVIRONMENTS_SCOPE.AVAILABLE"
@click="setScope($options.ENVIRONMENTS_SCOPE.AVAILABLE)"
>
<template #title>
<span>{{ $options.i18n.available }}</span>
<gl-badge size="sm" class="gl-tab-counter-badge">
{{ availableCount }}
</gl-badge>
</template>
</gl-tab>
<gl-tab
:query-param-value="$options.ENVIRONMENTS_SCOPE.STOPPED"
@click="setScope($options.ENVIRONMENTS_SCOPE.STOPPED)"
>
<template #title>
<span>{{ $options.i18n.stopped }}</span>
<gl-badge size="sm" class="gl-tab-counter-badge">
{{ stoppedCount }}
</gl-badge>
</template>
</gl-tab>
</gl-tabs>
<template v-if="hasEnvironments">
<environment-folder
v-for="folder in folders"
:key="folder.name"
class="gl-mb-3"
:scope="scope"
:nested-environment="folder"
/>
<environment-item
v-for="environment in environments"
:key="environment.name"
class="gl-mb-3 gl-border-gray-100 gl-border-1 gl-border-b-solid"
:environment="environment.latest"
@change="resetPolling"
/>
</template>
<empty-state v-else :help-path="helpPagePath" />
<gl-pagination
align="center"
:total-items="totalItems"
:per-page="itemsPerPage"
:value="page"
:next="$options.i18n.next"
:prev="$options.i18n.prev"
:label-previous-page="$options.prevPage"
:label-next-page="$options.nextPage"
:label-page="$options.goto"
@next="movePage('next')"
@previous="movePage('previous')"
@input="moveToPage"
/>
</div>
</template>
import Vue from 'vue'; import Vue from 'vue';
import VueApollo from 'vue-apollo'; import VueApollo from 'vue-apollo';
import createDefaultClient from '~/lib/graphql';
import { parseBoolean } from '../lib/utils/common_utils'; import { parseBoolean } from '../lib/utils/common_utils';
import Translate from '../vue_shared/translate'; import { apolloProvider } from './graphql/client';
import environmentsComponent from './components/environments_app.vue'; import EnvironmentsApp from './components/environments_app.vue';
Vue.use(Translate);
Vue.use(VueApollo); Vue.use(VueApollo);
const apolloProvider = new VueApollo({
defaultClient: createDefaultClient(),
});
export default (el) => { export default (el) => {
if (el) { if (el) {
const {
canCreateEnvironment,
endpoint,
newEnvironmentPath,
helpPagePath,
projectPath,
defaultBranchName,
projectId,
} = el.dataset;
return new Vue({ return new Vue({
el, el,
components: { apolloProvider: apolloProvider(endpoint),
environmentsComponent,
},
apolloProvider,
provide: { provide: {
projectPath: el.dataset.projectPath, projectPath,
projectId: el.dataset.projectId, defaultBranchName,
defaultBranchName: el.dataset.defaultBranchName, endpoint,
}, newEnvironmentPath,
data() { helpPagePath,
const environmentsData = el.dataset; projectId,
canCreateEnvironment: parseBoolean(canCreateEnvironment),
return {
endpoint: environmentsData.environmentsDataEndpoint,
newEnvironmentPath: environmentsData.newEnvironmentPath,
helpPagePath: environmentsData.helpPagePath,
canCreateEnvironment: parseBoolean(environmentsData.canCreateEnvironment),
};
}, },
render(createElement) { render(h) {
return createElement('environments-component', { return h(EnvironmentsApp);
props: {
endpoint: this.endpoint,
newEnvironmentPath: this.newEnvironmentPath,
helpPagePath: this.helpPagePath,
canCreateEnvironment: this.canCreateEnvironment,
},
});
}, },
}); });
} }
......
import Vue from 'vue';
import VueApollo from 'vue-apollo';
import { parseBoolean } from '../lib/utils/common_utils';
import { apolloProvider } from './graphql/client';
import EnvironmentsApp from './components/new_environments_app.vue';
Vue.use(VueApollo);
export default (el) => {
if (el) {
const {
canCreateEnvironment,
endpoint,
newEnvironmentPath,
helpPagePath,
projectPath,
defaultBranchName,
projectId,
} = el.dataset;
return new Vue({
el,
apolloProvider: apolloProvider(endpoint),
provide: {
projectPath,
defaultBranchName,
endpoint,
newEnvironmentPath,
helpPagePath,
projectId,
canCreateEnvironment: parseBoolean(canCreateEnvironment),
},
render(h) {
return h(EnvironmentsApp);
},
});
}
return null;
};
import initEnvironments from '~/environments/'; import initEnvironments from '~/environments/index';
import initNewEnvironments from '~/environments/new_index';
let el = document.getElementById('environments-list-view'); const el = document.getElementById('environments-table');
if (el) { initEnvironments(el);
initEnvironments(el);
} else {
el = document.getElementById('environments-table');
initNewEnvironments(el);
}
import { mount } from '@vue/test-utils';
import MockAdapter from 'axios-mock-adapter';
import DeployBoard from '~/environments/components/deploy_board.vue';
import EnvironmentsComponent from '~/environments/components/environments_app.vue';
import axios from '~/lib/utils/axios_utils';
import { environment } from './mock_data';
describe('Environment', () => {
let mock;
let wrapper;
const mockData = {
canCreateEnvironment: true,
endpoint: 'environments.json',
helpCanaryDeploymentsPath: 'help/canary-deployments',
helpPagePath: 'help',
lockPromotionSvgPath: '/assets/illustrations/lock-promotion.svg',
newEnvironmentPath: 'environments/new',
userCalloutsPath: '/callouts',
};
const createWrapper = () => {
wrapper = mount(EnvironmentsComponent, { propsData: mockData });
return axios.waitForAll();
};
const mockRequest = (environmentList) => {
mock.onGet(mockData.endpoint).reply(
200,
{
environments: environmentList,
stopped_count: 1,
available_count: 0,
},
{
'X-nExt-pAge': '2',
'x-page': '1',
'X-Per-Page': '1',
'X-Prev-Page': '',
'X-TOTAL': '37',
'X-Total-Pages': '2',
},
);
};
beforeEach(() => {
mock = new MockAdapter(axios);
});
afterEach(() => {
wrapper.destroy();
mock.restore();
});
describe('with paginated environments', () => {
beforeEach(() => {
mockRequest([environment]);
return createWrapper();
});
describe('deploy boards', () => {
beforeEach(() => {
const deployEnvironment = {
...environment,
rollout_status: {
status: 'found',
},
};
mockRequest([environment, deployEnvironment]);
return createWrapper();
});
it('should render deploy boards', () => {
expect(wrapper.findComponent(DeployBoard).exists()).toBe(true);
});
it('should render arrow to open deploy boards', () => {
expect(wrapper.find('.deploy-board-icon [data-testid="chevron-down-icon"]').exists()).toBe(
true,
);
});
});
});
});
...@@ -6,13 +6,13 @@ import createMockApollo from 'helpers/mock_apollo_helper'; ...@@ -6,13 +6,13 @@ import createMockApollo from 'helpers/mock_apollo_helper';
import { mountExtended } from 'helpers/vue_test_utils_helper'; import { mountExtended } from 'helpers/vue_test_utils_helper';
import { stubTransition } from 'helpers/stub_transition'; import { stubTransition } from 'helpers/stub_transition';
import { __, s__ } from '~/locale'; import { __, s__ } from '~/locale';
import EnvironmentsFolder from '~/environments/components/new_environment_folder.vue'; import EnvironmentsFolder from '~/environments/components/environment_folder.vue';
import EnvironmentItem from '~/environments/components/new_environment_item.vue'; import EnvironmentItem from '~/environments/components/new_environment_item.vue';
import { resolvedEnvironmentsApp, resolvedFolder } from './graphql/mock_data'; import { resolvedEnvironmentsApp, resolvedFolder } from './graphql/mock_data';
Vue.use(VueApollo); Vue.use(VueApollo);
describe('~/environments/components/new_environments_folder.vue', () => { describe('~/environments/components/environments_folder.vue', () => {
let wrapper; let wrapper;
let environmentFolderMock; let environmentFolderMock;
let nestedEnvironment; let nestedEnvironment;
......
import { GlTabs } from '@gitlab/ui'; import Vue, { nextTick } from 'vue';
import { mount, shallowMount } from '@vue/test-utils'; import VueApollo from 'vue-apollo';
import MockAdapter from 'axios-mock-adapter'; import { GlPagination } from '@gitlab/ui';
import { nextTick } from 'vue'; import { mountExtended } from 'helpers/vue_test_utils_helper';
import { extendedWrapper } from 'helpers/vue_test_utils_helper'; import createMockApollo from 'helpers/mock_apollo_helper';
import Container from '~/environments/components/container.vue'; import waitForPromises from 'helpers/wait_for_promises';
import DeployBoard from '~/environments/components/deploy_board.vue'; import setWindowLocation from 'helpers/set_window_location_helper';
import EmptyState from '~/environments/components/empty_state.vue'; import { sprintf, __, s__ } from '~/locale';
import EnableReviewAppModal from '~/environments/components/enable_review_app_modal.vue';
import EnvironmentsApp from '~/environments/components/environments_app.vue'; import EnvironmentsApp from '~/environments/components/environments_app.vue';
import axios from '~/lib/utils/axios_utils'; import EnvironmentsFolder from '~/environments/components/environment_folder.vue';
import * as urlUtils from '~/lib/utils/url_utility'; import EnvironmentsItem from '~/environments/components/new_environment_item.vue';
import { environment, folder } from './mock_data'; import EmptyState from '~/environments/components/empty_state.vue';
import StopEnvironmentModal from '~/environments/components/stop_environment_modal.vue';
import CanaryUpdateModal from '~/environments/components/canary_update_modal.vue';
import { resolvedEnvironmentsApp, resolvedFolder, resolvedEnvironment } from './graphql/mock_data';
describe('Environment', () => { Vue.use(VueApollo);
let mock;
let wrapper;
const mockData = { describe('~/environments/components/environments_app.vue', () => {
endpoint: 'environments.json', let wrapper;
canCreateEnvironment: true, let environmentAppMock;
newEnvironmentPath: 'environments/new', let environmentFolderMock;
helpPagePath: 'help', let paginationMock;
userCalloutsPath: '/callouts', let environmentToStopMock;
lockPromotionSvgPath: '/assets/illustrations/lock-promotion.svg', let environmentToChangeCanaryMock;
helpCanaryDeploymentsPath: 'help/canary-deployments', let weightMock;
const createApolloProvider = () => {
const mockResolvers = {
Query: {
environmentApp: environmentAppMock,
folder: environmentFolderMock,
pageInfo: paginationMock,
environmentToStop: environmentToStopMock,
environmentToDelete: jest.fn().mockResolvedValue(resolvedEnvironment),
environmentToRollback: jest.fn().mockResolvedValue(resolvedEnvironment),
environmentToChangeCanary: environmentToChangeCanaryMock,
weight: weightMock,
},
};
return createMockApollo([], mockResolvers);
}; };
const mockRequest = (response, body) => { const createWrapper = ({ provide = {}, apolloProvider } = {}) =>
mock.onGet(mockData.endpoint).reply(response, body, { mountExtended(EnvironmentsApp, {
'X-nExt-pAge': '2', provide: {
'x-page': '1', newEnvironmentPath: '/environments/new',
'X-Per-Page': '1', canCreateEnvironment: true,
'X-Prev-Page': '', defaultBranchName: 'main',
'X-TOTAL': '37', helpPagePath: '/help',
'X-Total-Pages': '2', projectId: '1',
...provide,
},
apolloProvider,
}); });
};
const createWrapper = (shallow = false, props = {}) => { const createWrapperWithMocked = async ({
const fn = shallow ? shallowMount : mount; provide = {},
wrapper = extendedWrapper(fn(EnvironmentsApp, { propsData: { ...mockData, ...props } })); environmentsApp,
return axios.waitForAll(); folder,
environmentToStop = {},
environmentToChangeCanary = {},
weight = 0,
pageInfo = {
total: 20,
perPage: 5,
nextPage: 3,
page: 2,
previousPage: 1,
__typename: 'LocalPageInfo',
},
location = '?scope=available&page=2',
}) => {
setWindowLocation(location);
environmentAppMock.mockReturnValue(environmentsApp);
environmentFolderMock.mockReturnValue(folder);
paginationMock.mockReturnValue(pageInfo);
environmentToStopMock.mockReturnValue(environmentToStop);
environmentToChangeCanaryMock.mockReturnValue(environmentToChangeCanary);
weightMock.mockReturnValue(weight);
const apolloProvider = createApolloProvider();
wrapper = createWrapper({ apolloProvider, provide });
await waitForPromises();
await nextTick();
}; };
const findEnableReviewAppButton = () => wrapper.findByTestId('enable-review-app');
const findEnableReviewAppModal = () => wrapper.findAll(EnableReviewAppModal);
const findNewEnvironmentButton = () => wrapper.findByTestId('new-environment');
const findEnvironmentsTabAvailable = () => wrapper.find('.js-environments-tab-available > a');
const findEnvironmentsTabStopped = () => wrapper.find('.js-environments-tab-stopped > a');
beforeEach(() => { beforeEach(() => {
mock = new MockAdapter(axios); environmentAppMock = jest.fn();
environmentFolderMock = jest.fn();
environmentToStopMock = jest.fn();
environmentToChangeCanaryMock = jest.fn();
weightMock = jest.fn();
paginationMock = jest.fn();
}); });
afterEach(() => { afterEach(() => {
wrapper.destroy(); wrapper.destroy();
mock.restore();
}); });
describe('successful request', () => { it('should request available environments if the scope is invalid', async () => {
describe('without environments', () => { await createWrapperWithMocked({
beforeEach(() => { environmentsApp: resolvedEnvironmentsApp,
mockRequest(200, { environments: [] }); folder: resolvedFolder,
return createWrapper(); location: '?scope=bad&page=2',
}); });
it('should render the empty state', () => { expect(environmentAppMock).toHaveBeenCalledWith(
expect(wrapper.find(EmptyState).exists()).toBe(true); expect.anything(),
}); expect.objectContaining({ scope: 'available', page: 2 }),
expect.anything(),
expect.anything(),
);
});
it('should show all the folders that are fetched', async () => {
await createWrapperWithMocked({
environmentsApp: resolvedEnvironmentsApp,
folder: resolvedFolder,
}); });
describe('with paginated environments', () => { const text = wrapper.findAllComponents(EnvironmentsFolder).wrappers.map((w) => w.text());
const environmentList = [environment];
beforeEach(() => { expect(text).toContainEqual(expect.stringMatching('review'));
mockRequest(200, { expect(text).not.toContainEqual(expect.stringMatching('production'));
environments: environmentList, });
stopped_count: 1,
available_count: 0,
});
return createWrapper();
});
it('should render a container table with environments', () => { it('should show all the environments that are fetched', async () => {
const containerTable = wrapper.find(Container); await createWrapperWithMocked({
environmentsApp: resolvedEnvironmentsApp,
folder: resolvedFolder,
});
expect(containerTable.exists()).toBe(true); const text = wrapper.findAllComponents(EnvironmentsItem).wrappers.map((w) => w.text());
expect(containerTable.props('environments').length).toEqual(environmentList.length);
expect(containerTable.find('.environment-name').text()).toEqual(environmentList[0].name);
});
describe('pagination', () => { expect(text).not.toContainEqual(expect.stringMatching('review'));
it('should render pagination', () => { expect(text).toContainEqual(expect.stringMatching('production'));
expect(wrapper.findAll('.gl-pagination li').length).toEqual(9); });
});
it('should make an API request when page is clicked', () => {
jest.spyOn(wrapper.vm, 'updateContent').mockImplementation(() => {});
wrapper.find('.gl-pagination li:nth-child(3) .page-link').trigger('click');
expect(wrapper.vm.updateContent).toHaveBeenCalledWith({
scope: 'available',
page: '2',
nested: true,
});
});
it('should make an API request when using tabs', () => {
jest.spyOn(wrapper.vm, 'updateContent').mockImplementation(() => {});
findEnvironmentsTabStopped().trigger('click');
expect(wrapper.vm.updateContent).toHaveBeenCalledWith({
scope: 'stopped',
page: '1',
nested: true,
});
});
it('should not make the same API request when clicking on the current scope tab', () => {
// component starts at available
jest.spyOn(wrapper.vm, 'updateContent').mockImplementation(() => {});
findEnvironmentsTabAvailable().trigger('click');
expect(wrapper.vm.updateContent).toHaveBeenCalledTimes(0);
});
});
describe('deploy boards', () => { it('should show an empty state with no environments', async () => {
beforeEach(() => { await createWrapperWithMocked({
const deployEnvironment = { environmentsApp: { ...resolvedEnvironmentsApp, environments: [] },
...environment,
rollout_status: {
status: 'found',
},
};
mockRequest(200, {
environments: [deployEnvironment],
stopped_count: 1,
available_count: 0,
});
return createWrapper();
});
it('should render deploy boards', () => {
expect(wrapper.find(DeployBoard).exists()).toBe(true);
});
it('should render arrow to open deploy boards', () => {
expect(
wrapper.find('.deploy-board-icon [data-testid="chevron-down-icon"]').exists(),
).toBe(true);
});
});
}); });
expect(wrapper.findComponent(EmptyState).exists()).toBe(true);
}); });
describe('unsuccessful request', () => { it('should show a button to create a new environment', async () => {
beforeEach(() => { await createWrapperWithMocked({
mockRequest(500, {}); environmentsApp: resolvedEnvironmentsApp,
return createWrapper(); folder: resolvedFolder,
}); });
it('should render empty state', () => { const button = wrapper.findByRole('link', { name: s__('Environments|New environment') });
expect(wrapper.find(EmptyState).exists()).toBe(true); expect(button.attributes('href')).toBe('/environments/new');
});
}); });
describe('expandable folders', () => { it('should not show a button to create a new environment if the user has no permissions', async () => {
beforeEach(() => { await createWrapperWithMocked({
mockRequest(200, { environmentsApp: resolvedEnvironmentsApp,
environments: [folder], folder: resolvedFolder,
stopped_count: 1, provide: { canCreateEnvironment: false, newEnvironmentPath: '' },
available_count: 0, });
});
mock.onGet(environment.folder_path).reply(200, { environments: [environment] }); const button = wrapper.findByRole('link', { name: s__('Environments|New environment') });
expect(button.exists()).toBe(false);
});
return createWrapper().then(() => { it('should show a button to open the review app modal', async () => {
// open folder await createWrapperWithMocked({
wrapper.find('.folder-name').trigger('click'); environmentsApp: resolvedEnvironmentsApp,
return axios.waitForAll(); folder: resolvedFolder,
});
}); });
it('should open a closed folder', () => { const button = wrapper.findByRole('button', { name: s__('Environments|Enable review app') });
expect(wrapper.find('.folder-icon[data-testid="chevron-right-icon"]').exists()).toBe(false); button.trigger('click');
});
it('should close an opened folder', async () => { await nextTick();
expect(wrapper.find('.folder-icon[data-testid="chevron-down-icon"]').exists()).toBe(true);
// close folder expect(wrapper.findByText(s__('ReviewApp|Enable Review App')).exists()).toBe(true);
wrapper.find('.folder-name').trigger('click'); });
await nextTick();
expect(wrapper.find('.folder-icon[data-testid="chevron-down-icon"]').exists()).toBe(false);
});
it('should show children environments', () => { it('should not show a button to open the review app modal if review apps are configured', async () => {
expect(wrapper.findAll('.js-child-row').length).toEqual(1); await createWrapperWithMocked({
environmentsApp: {
...resolvedEnvironmentsApp,
reviewApp: { canSetupReviewApp: false },
},
folder: resolvedFolder,
}); });
it('should show a button to show all environments', () => { const button = wrapper.findByRole('button', { name: s__('Environments|Enable review app') });
expect(wrapper.find('.text-center > a.btn').text()).toContain('Show all'); expect(button.exists()).toBe(false);
});
}); });
describe('environment button', () => { describe('tabs', () => {
describe('when user can create environment', () => { it('should show tabs for available and stopped environmets', async () => {
beforeEach(() => { await createWrapperWithMocked({
mockRequest(200, { environments: [] }); environmentsApp: resolvedEnvironmentsApp,
return createWrapper(true); folder: resolvedFolder,
}); });
it('should render', () => { const [available, stopped] = wrapper.findAllByRole('tab').wrappers;
expect(findNewEnvironmentButton().exists()).toBe(true);
expect(available.text()).toContain(__('Available'));
expect(available.text()).toContain(resolvedEnvironmentsApp.availableCount);
expect(stopped.text()).toContain(__('Stopped'));
expect(stopped.text()).toContain(resolvedEnvironmentsApp.stoppedCount);
});
it('should change the requested scope on tab change', async () => {
await createWrapperWithMocked({
environmentsApp: resolvedEnvironmentsApp,
folder: resolvedFolder,
});
const stopped = wrapper.findByRole('tab', {
name: `${__('Stopped')} ${resolvedEnvironmentsApp.stoppedCount}`,
}); });
stopped.trigger('click');
await nextTick();
await waitForPromises();
expect(environmentAppMock).toHaveBeenCalledWith(
expect.anything(),
expect.objectContaining({ scope: 'stopped', page: 1 }),
expect.anything(),
expect.anything(),
);
}); });
});
describe('when user can not create environment', () => { describe('modals', () => {
beforeEach(() => { it('should pass the environment to stop to the stop environment modal', async () => {
mockRequest(200, { environments: [] }); await createWrapperWithMocked({
return createWrapper(true, { ...mockData, canCreateEnvironment: false }); environmentsApp: resolvedEnvironmentsApp,
folder: resolvedFolder,
environmentToStop: resolvedEnvironment,
}); });
it('should not render', () => { const modal = wrapper.findComponent(StopEnvironmentModal);
expect(findNewEnvironmentButton().exists()).toBe(false);
expect(modal.props('environment')).toMatchObject(resolvedEnvironment);
});
it('should pass the environment to change canary to the canary update modal', async () => {
await createWrapperWithMocked({
environmentsApp: resolvedEnvironmentsApp,
folder: resolvedFolder,
environmentToChangeCanary: resolvedEnvironment,
weight: 10,
}); });
const modal = wrapper.findComponent(CanaryUpdateModal);
expect(modal.props('environment')).toMatchObject(resolvedEnvironment);
}); });
}); });
describe('review app modal', () => { describe('pagination', () => {
describe('when it is not possible to enable a review app', () => { it('should sync page from query params on load', async () => {
beforeEach(() => { await createWrapperWithMocked({
mockRequest(200, { environments: [] }); environmentsApp: resolvedEnvironmentsApp,
return createWrapper(true); folder: resolvedFolder,
}); });
it('should not render the enable review app button', () => { expect(wrapper.findComponent(GlPagination).props('value')).toBe(2);
expect(findEnableReviewAppButton().exists()).toBe(false); });
});
it('should not render a review app modal', () => { it('should change the requested page on next page click', async () => {
const modal = findEnableReviewAppModal(); await createWrapperWithMocked({
expect(modal).toHaveLength(0); environmentsApp: resolvedEnvironmentsApp,
expect(modal.exists()).toBe(false); folder: resolvedFolder,
});
const next = wrapper.findByRole('link', {
name: __('Go to next page'),
}); });
next.trigger('click');
await nextTick();
await waitForPromises();
expect(environmentAppMock).toHaveBeenCalledWith(
expect.anything(),
expect.objectContaining({ page: 3 }),
expect.anything(),
expect.anything(),
);
}); });
describe('when it is possible to enable a review app', () => { it('should change the requested page on previous page click', async () => {
beforeEach(() => { await createWrapperWithMocked({
mockRequest(200, { environments: [], review_app: { can_setup_review_app: true } }); environmentsApp: resolvedEnvironmentsApp,
return createWrapper(true); folder: resolvedFolder,
});
const prev = wrapper.findByRole('link', {
name: __('Go to previous page'),
}); });
it('should render the enable review app button', () => { prev.trigger('click');
expect(findEnableReviewAppButton().exists()).toBe(true);
expect(findEnableReviewAppButton().text()).toContain('Enable review app'); await nextTick();
await waitForPromises();
expect(environmentAppMock).toHaveBeenCalledWith(
expect.anything(),
expect.objectContaining({ page: 1 }),
expect.anything(),
expect.anything(),
);
});
it('should change the requested page on specific page click', async () => {
await createWrapperWithMocked({
environmentsApp: resolvedEnvironmentsApp,
folder: resolvedFolder,
}); });
it('should render only one review app modal', () => { const page = 1;
const modal = findEnableReviewAppModal(); const pageButton = wrapper.findByRole('link', {
expect(modal).toHaveLength(1); name: sprintf(__('Go to page %{page}'), { page }),
expect(modal.at(0).exists()).toBe(true);
}); });
});
});
describe('tabs', () => { pageButton.trigger('click');
beforeEach(() => {
mockRequest(200, { environments: [] }); await nextTick();
jest await waitForPromises();
.spyOn(urlUtils, 'getParameterByName')
.mockImplementation((param) => (param === 'scope' ? 'stopped' : null)); expect(environmentAppMock).toHaveBeenCalledWith(
return createWrapper(true); expect.anything(),
expect.objectContaining({ page }),
expect.anything(),
expect.anything(),
);
}); });
it('selects the tab for the parameter', () => { it('should sync the query params to the new page', async () => {
expect(wrapper.findComponent(GlTabs).attributes('value')).toBe('1'); await createWrapperWithMocked({
environmentsApp: resolvedEnvironmentsApp,
folder: resolvedFolder,
});
const next = wrapper.findByRole('link', {
name: __('Go to next page'),
});
next.trigger('click');
await nextTick();
expect(window.location.search).toBe('?scope=available&page=3');
}); });
}); });
}); });
import Vue, { nextTick } from 'vue';
import VueApollo from 'vue-apollo';
import { GlPagination } from '@gitlab/ui';
import { mountExtended } from 'helpers/vue_test_utils_helper';
import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
import setWindowLocation from 'helpers/set_window_location_helper';
import { sprintf, __, s__ } from '~/locale';
import EnvironmentsApp from '~/environments/components/new_environments_app.vue';
import EnvironmentsFolder from '~/environments/components/new_environment_folder.vue';
import EnvironmentsItem from '~/environments/components/new_environment_item.vue';
import EmptyState from '~/environments/components/empty_state.vue';
import StopEnvironmentModal from '~/environments/components/stop_environment_modal.vue';
import CanaryUpdateModal from '~/environments/components/canary_update_modal.vue';
import { resolvedEnvironmentsApp, resolvedFolder, resolvedEnvironment } from './graphql/mock_data';
Vue.use(VueApollo);
describe('~/environments/components/new_environments_app.vue', () => {
let wrapper;
let environmentAppMock;
let environmentFolderMock;
let paginationMock;
let environmentToStopMock;
let environmentToChangeCanaryMock;
let weightMock;
const createApolloProvider = () => {
const mockResolvers = {
Query: {
environmentApp: environmentAppMock,
folder: environmentFolderMock,
pageInfo: paginationMock,
environmentToStop: environmentToStopMock,
environmentToDelete: jest.fn().mockResolvedValue(resolvedEnvironment),
environmentToRollback: jest.fn().mockResolvedValue(resolvedEnvironment),
environmentToChangeCanary: environmentToChangeCanaryMock,
weight: weightMock,
},
};
return createMockApollo([], mockResolvers);
};
const createWrapper = ({ provide = {}, apolloProvider } = {}) =>
mountExtended(EnvironmentsApp, {
provide: {
newEnvironmentPath: '/environments/new',
canCreateEnvironment: true,
defaultBranchName: 'main',
helpPagePath: '/help',
projectId: '1',
...provide,
},
apolloProvider,
});
const createWrapperWithMocked = async ({
provide = {},
environmentsApp,
folder,
environmentToStop = {},
environmentToChangeCanary = {},
weight = 0,
pageInfo = {
total: 20,
perPage: 5,
nextPage: 3,
page: 2,
previousPage: 1,
__typename: 'LocalPageInfo',
},
location = '?scope=available&page=2',
}) => {
setWindowLocation(location);
environmentAppMock.mockReturnValue(environmentsApp);
environmentFolderMock.mockReturnValue(folder);
paginationMock.mockReturnValue(pageInfo);
environmentToStopMock.mockReturnValue(environmentToStop);
environmentToChangeCanaryMock.mockReturnValue(environmentToChangeCanary);
weightMock.mockReturnValue(weight);
const apolloProvider = createApolloProvider();
wrapper = createWrapper({ apolloProvider, provide });
await waitForPromises();
await nextTick();
};
beforeEach(() => {
environmentAppMock = jest.fn();
environmentFolderMock = jest.fn();
environmentToStopMock = jest.fn();
environmentToChangeCanaryMock = jest.fn();
weightMock = jest.fn();
paginationMock = jest.fn();
});
afterEach(() => {
wrapper.destroy();
});
it('should request available environments if the scope is invalid', async () => {
await createWrapperWithMocked({
environmentsApp: resolvedEnvironmentsApp,
folder: resolvedFolder,
location: '?scope=bad&page=2',
});
expect(environmentAppMock).toHaveBeenCalledWith(
expect.anything(),
expect.objectContaining({ scope: 'available', page: 2 }),
expect.anything(),
expect.anything(),
);
});
it('should show all the folders that are fetched', async () => {
await createWrapperWithMocked({
environmentsApp: resolvedEnvironmentsApp,
folder: resolvedFolder,
});
const text = wrapper.findAllComponents(EnvironmentsFolder).wrappers.map((w) => w.text());
expect(text).toContainEqual(expect.stringMatching('review'));
expect(text).not.toContainEqual(expect.stringMatching('production'));
});
it('should show all the environments that are fetched', async () => {
await createWrapperWithMocked({
environmentsApp: resolvedEnvironmentsApp,
folder: resolvedFolder,
});
const text = wrapper.findAllComponents(EnvironmentsItem).wrappers.map((w) => w.text());
expect(text).not.toContainEqual(expect.stringMatching('review'));
expect(text).toContainEqual(expect.stringMatching('production'));
});
it('should show an empty state with no environments', async () => {
await createWrapperWithMocked({
environmentsApp: { ...resolvedEnvironmentsApp, environments: [] },
});
expect(wrapper.findComponent(EmptyState).exists()).toBe(true);
});
it('should show a button to create a new environment', async () => {
await createWrapperWithMocked({
environmentsApp: resolvedEnvironmentsApp,
folder: resolvedFolder,
});
const button = wrapper.findByRole('link', { name: s__('Environments|New environment') });
expect(button.attributes('href')).toBe('/environments/new');
});
it('should not show a button to create a new environment if the user has no permissions', async () => {
await createWrapperWithMocked({
environmentsApp: resolvedEnvironmentsApp,
folder: resolvedFolder,
provide: { canCreateEnvironment: false, newEnvironmentPath: '' },
});
const button = wrapper.findByRole('link', { name: s__('Environments|New environment') });
expect(button.exists()).toBe(false);
});
it('should show a button to open the review app modal', async () => {
await createWrapperWithMocked({
environmentsApp: resolvedEnvironmentsApp,
folder: resolvedFolder,
});
const button = wrapper.findByRole('button', { name: s__('Environments|Enable review app') });
button.trigger('click');
await nextTick();
expect(wrapper.findByText(s__('ReviewApp|Enable Review App')).exists()).toBe(true);
});
it('should not show a button to open the review app modal if review apps are configured', async () => {
await createWrapperWithMocked({
environmentsApp: {
...resolvedEnvironmentsApp,
reviewApp: { canSetupReviewApp: false },
},
folder: resolvedFolder,
});
const button = wrapper.findByRole('button', { name: s__('Environments|Enable review app') });
expect(button.exists()).toBe(false);
});
describe('tabs', () => {
it('should show tabs for available and stopped environmets', async () => {
await createWrapperWithMocked({
environmentsApp: resolvedEnvironmentsApp,
folder: resolvedFolder,
});
const [available, stopped] = wrapper.findAllByRole('tab').wrappers;
expect(available.text()).toContain(__('Available'));
expect(available.text()).toContain(resolvedEnvironmentsApp.availableCount);
expect(stopped.text()).toContain(__('Stopped'));
expect(stopped.text()).toContain(resolvedEnvironmentsApp.stoppedCount);
});
it('should change the requested scope on tab change', async () => {
await createWrapperWithMocked({
environmentsApp: resolvedEnvironmentsApp,
folder: resolvedFolder,
});
const stopped = wrapper.findByRole('tab', {
name: `${__('Stopped')} ${resolvedEnvironmentsApp.stoppedCount}`,
});
stopped.trigger('click');
await nextTick();
await waitForPromises();
expect(environmentAppMock).toHaveBeenCalledWith(
expect.anything(),
expect.objectContaining({ scope: 'stopped', page: 1 }),
expect.anything(),
expect.anything(),
);
});
});
describe('modals', () => {
it('should pass the environment to stop to the stop environment modal', async () => {
await createWrapperWithMocked({
environmentsApp: resolvedEnvironmentsApp,
folder: resolvedFolder,
environmentToStop: resolvedEnvironment,
});
const modal = wrapper.findComponent(StopEnvironmentModal);
expect(modal.props('environment')).toMatchObject(resolvedEnvironment);
});
it('should pass the environment to change canary to the canary update modal', async () => {
await createWrapperWithMocked({
environmentsApp: resolvedEnvironmentsApp,
folder: resolvedFolder,
environmentToChangeCanary: resolvedEnvironment,
weight: 10,
});
const modal = wrapper.findComponent(CanaryUpdateModal);
expect(modal.props('environment')).toMatchObject(resolvedEnvironment);
});
});
describe('pagination', () => {
it('should sync page from query params on load', async () => {
await createWrapperWithMocked({
environmentsApp: resolvedEnvironmentsApp,
folder: resolvedFolder,
});
expect(wrapper.findComponent(GlPagination).props('value')).toBe(2);
});
it('should change the requested page on next page click', async () => {
await createWrapperWithMocked({
environmentsApp: resolvedEnvironmentsApp,
folder: resolvedFolder,
});
const next = wrapper.findByRole('link', {
name: __('Go to next page'),
});
next.trigger('click');
await nextTick();
await waitForPromises();
expect(environmentAppMock).toHaveBeenCalledWith(
expect.anything(),
expect.objectContaining({ page: 3 }),
expect.anything(),
expect.anything(),
);
});
it('should change the requested page on previous page click', async () => {
await createWrapperWithMocked({
environmentsApp: resolvedEnvironmentsApp,
folder: resolvedFolder,
});
const prev = wrapper.findByRole('link', {
name: __('Go to previous page'),
});
prev.trigger('click');
await nextTick();
await waitForPromises();
expect(environmentAppMock).toHaveBeenCalledWith(
expect.anything(),
expect.objectContaining({ page: 1 }),
expect.anything(),
expect.anything(),
);
});
it('should change the requested page on specific page click', async () => {
await createWrapperWithMocked({
environmentsApp: resolvedEnvironmentsApp,
folder: resolvedFolder,
});
const page = 1;
const pageButton = wrapper.findByRole('link', {
name: sprintf(__('Go to page %{page}'), { page }),
});
pageButton.trigger('click');
await nextTick();
await waitForPromises();
expect(environmentAppMock).toHaveBeenCalledWith(
expect.anything(),
expect.objectContaining({ page }),
expect.anything(),
expect.anything(),
);
});
it('should sync the query params to the new page', async () => {
await createWrapperWithMocked({
environmentsApp: resolvedEnvironmentsApp,
folder: resolvedFolder,
});
const next = wrapper.findByRole('link', {
name: __('Go to next page'),
});
next.trigger('click');
await nextTick();
expect(window.location.search).toBe('?scope=available&page=3');
});
});
});
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