Skip to content
代码片段 群组 项目
未验证 提交 f285da05 编辑于 作者: Janis Altherr's avatar Janis Altherr 提交者: GitLab
浏览文件

Merge branch...

Merge branch '458159-move-multiple-deployments-limit-from-top-level-namespace-to-project-if-unique_domain-is' into 'master' 

Move Multiple Deployments limit from top-level namespace to project if  `unique_domain` is enabled.

See merge request https://gitlab.com/gitlab-org/gitlab/-/merge_requests/173818



Merged-by: default avatarJanis Altherr <jaltherr@gitlab.com>
Approved-by: default avatarBuck O'Leary <bucoleary@gitlab.com>
Approved-by: default avatarSavas Vedova <svedova@gitlab.com>
Reviewed-by: default avatarSavas Vedova <svedova@gitlab.com>
Reviewed-by: default avatarJanis Altherr <jaltherr@gitlab.com>
No related branches found
No related tags found
无相关合并请求
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
import { GlCard, GlTableLite, GlIcon, GlBadge, GlSprintf, GlLink, GlAvatar } from '@gitlab/ui'; import { GlCard, GlTableLite, GlIcon, GlBadge, GlSprintf, GlLink, GlAvatar } from '@gitlab/ui';
import NumberToHumanSize from '~/vue_shared/components/number_to_human_size/number_to_human_size.vue'; import NumberToHumanSize from '~/vue_shared/components/number_to_human_size/number_to_human_size.vue';
import { SHORT_DATE_FORMAT_WITH_TIME } from '~/vue_shared/constants'; import { SHORT_DATE_FORMAT_WITH_TIME } from '~/vue_shared/constants';
import { PROJECT_VIEW_TYPE } from '~/usage_quotas/constants';
import UserDate from '~/vue_shared/components/user_date.vue'; import UserDate from '~/vue_shared/components/user_date.vue';
import { joinPaths } from '~/lib/utils/url_utility'; import { joinPaths } from '~/lib/utils/url_utility';
import { s__, sprintf } from '~/locale'; import { s__, sprintf } from '~/locale';
...@@ -21,6 +22,7 @@ export default { ...@@ -21,6 +22,7 @@ export default {
GlLink, GlLink,
GlAvatar, GlAvatar,
}, },
inject: ['viewType'],
props: { props: {
project: { project: {
type: Object, type: Object,
...@@ -28,6 +30,7 @@ export default { ...@@ -28,6 +30,7 @@ export default {
}, },
}, },
static: { static: {
PROJECT_VIEW_TYPE,
SHORT_DATE_FORMAT_WITH_TIME, SHORT_DATE_FORMAT_WITH_TIME,
}, },
i18n: { i18n: {
...@@ -85,6 +88,9 @@ export default { ...@@ -85,6 +88,9 @@ export default {
}, },
], ],
computed: { computed: {
isSingleProjectView() {
return this.viewType === this.$options.static.PROJECT_VIEW_TYPE;
},
pagesUrl() { pagesUrl() {
return joinPaths(gon.relative_url_root || '', '/', this.project.fullPath, 'pages'); return joinPaths(gon.relative_url_root || '', '/', this.project.fullPath, 'pages');
}, },
...@@ -115,7 +121,7 @@ export default { ...@@ -115,7 +121,7 @@ export default {
<template> <template>
<gl-card body-class="gl-p-0"> <gl-card body-class="gl-p-0">
<template #header> <template v-if="!isSingleProjectView" #header>
<div class="gl-flex gl-items-center gl-justify-between" data-testid="project-name"> <div class="gl-flex gl-items-center gl-justify-between" data-testid="project-name">
<gl-link :href="pagesUrl" class="gl-flex gl-items-center gl-no-underline"> <gl-link :href="pagesUrl" class="gl-flex gl-items-center gl-no-underline">
<gl-avatar <gl-avatar
......
<script> <script>
import { GlEmptyState, GlLoadingIcon, GlAlert } from '@gitlab/ui'; import { GlEmptyState, GlLoadingIcon, GlAlert } from '@gitlab/ui';
import EMPTY_STATE_SVG_URL from '@gitlab/svgs/dist/illustrations/empty-state/empty-search-md.svg?url'; import EMPTY_STATE_SVG_URL from '@gitlab/svgs/dist/illustrations/empty-state/empty-search-md.svg?url';
import { PROJECT_VIEW_TYPE } from '~/usage_quotas/constants';
import GetProjectPagesDeployments from '~/gitlab_pages/queries/get_project_pages_deployments.graphql';
import GetNamespacePagesDeployments from '../graphql/pages_deployments.query.graphql'; import GetNamespacePagesDeployments from '../graphql/pages_deployments.query.graphql';
import ProjectView from './project.vue'; import ProjectView from './project.vue';
...@@ -13,7 +15,8 @@ export default { ...@@ -13,7 +15,8 @@ export default {
GlAlert, GlAlert,
}, },
EMPTY_STATE_SVG_URL, EMPTY_STATE_SVG_URL,
inject: ['fullPath'], PROJECT_VIEW_TYPE,
inject: ['fullPath', 'viewType'],
props: { props: {
sort: { sort: {
type: String, type: String,
...@@ -23,14 +26,36 @@ export default { ...@@ -23,14 +26,36 @@ export default {
}, },
data() { data() {
return { return {
project: null,
projects: {}, projects: {},
resultsPerPage: 15, resultsPerPage: 15,
error: null, error: null,
}; };
}, },
apollo: { apollo: {
project: {
query: GetProjectPagesDeployments,
skip() {
return !this.isProjectView;
},
variables() {
return {
fullPath: this.fullPath,
first: this.resultsPerPage,
sort: this.sort,
active: true,
versioned: true,
};
},
error(error) {
this.error = error;
},
},
projects: { projects: {
query: GetNamespacePagesDeployments, query: GetNamespacePagesDeployments,
skip() {
return this.isProjectView;
},
variables() { variables() {
return { return {
fullPath: this.fullPath, fullPath: this.fullPath,
...@@ -51,7 +76,13 @@ export default { ...@@ -51,7 +76,13 @@ export default {
}, },
}, },
computed: { computed: {
isProjectView() {
return this.viewType === this.$options.PROJECT_VIEW_TYPE;
},
hasResults() { hasResults() {
if (this.isProjectView) {
return this.project.pagesDeployments.nodes?.length;
}
return this.projects?.length; return this.projects?.length;
}, },
}, },
...@@ -65,15 +96,22 @@ export default { ...@@ -65,15 +96,22 @@ export default {
{{ s__('Pages|An error occurred trying to load the Pages deployments.') }} {{ s__('Pages|An error occurred trying to load the Pages deployments.') }}
</gl-alert> </gl-alert>
<gl-empty-state <gl-empty-state
v-else-if="!hasResults" v-else-if="!isProjectView && !hasResults"
:title="__('No projects found')" :title="__('No projects found')"
:description=" :description="
s__('Pages|We did not find any projects with parallel Pages deployments in this namespace.') s__('Pages|We did not find any projects with parallel Pages deployments in this namespace.')
" "
:svg-path="$options.EMPTY_STATE_SVG_URL" :svg-path="$options.EMPTY_STATE_SVG_URL"
/> />
<gl-empty-state
v-else-if="isProjectView && !hasResults"
:title="__('No parallel deployments')"
:description="s__('Pages|There are no active parallel Pages deployments in this project.')"
:svg-path="$options.EMPTY_STATE_SVG_URL"
/>
<div v-else class="gl-flex gl-flex-col gl-gap-4"> <div v-else class="gl-flex gl-flex-col gl-gap-4">
<project-view v-for="node in projects" :key="node.id" :project="node" /> <project-view v-if="isProjectView" :project="project" />
<project-view v-for="node in projects" v-else :key="node.id" :project="node" />
</div> </div>
</div> </div>
</template> </template>
...@@ -13,7 +13,7 @@ export const parseProvideData = (el) => { ...@@ -13,7 +13,7 @@ export const parseProvideData = (el) => {
}; };
}; };
export const getPagesTabMetadata = () => { export const getPagesTabMetadata = ({ viewType } = {}) => {
const el = document.querySelector(PAGES_TAB_METADATA_EL_SELECTOR); const el = document.querySelector(PAGES_TAB_METADATA_EL_SELECTOR);
if (!el) return false; if (!el) return false;
...@@ -25,7 +25,10 @@ export const getPagesTabMetadata = () => { ...@@ -25,7 +25,10 @@ export const getPagesTabMetadata = () => {
component: { component: {
name: 'PagesDeploymentsTab', name: 'PagesDeploymentsTab',
apolloProvider, apolloProvider,
provide: parseProvideData(el), provide: {
viewType,
...parseProvideData(el),
},
render(createElement) { render(createElement) {
return createElement(PagesDeploymentsApp); return createElement(PagesDeploymentsApp);
}, },
......
...@@ -2,9 +2,11 @@ import { PROJECT_VIEW_TYPE } from '~/usage_quotas/constants'; ...@@ -2,9 +2,11 @@ import { PROJECT_VIEW_TYPE } from '~/usage_quotas/constants';
import { getStorageTabMetadata } from '~/usage_quotas/storage/tab_metadata'; import { getStorageTabMetadata } from '~/usage_quotas/storage/tab_metadata';
import { getTransferTabMetadata } from './transfer/tab_metadata'; import { getTransferTabMetadata } from './transfer/tab_metadata';
import { getObservabilityTabMetadata } from './observability/tab_metadata'; import { getObservabilityTabMetadata } from './observability/tab_metadata';
import { getPagesTabMetadata } from './pages/tab_metadata';
export const usageQuotasTabsMetadata = [ export const usageQuotasTabsMetadata = [
getStorageTabMetadata({ viewType: PROJECT_VIEW_TYPE }), getStorageTabMetadata({ viewType: PROJECT_VIEW_TYPE }),
getTransferTabMetadata({ viewType: PROJECT_VIEW_TYPE }), getTransferTabMetadata({ viewType: PROJECT_VIEW_TYPE }),
getObservabilityTabMetadata(), getObservabilityTabMetadata(),
getPagesTabMetadata({ viewType: PROJECT_VIEW_TYPE }),
].filter(Boolean); ].filter(Boolean);
...@@ -130,6 +130,75 @@ export const getNamespacePagesDeploymentsMockData = { ...@@ -130,6 +130,75 @@ export const getNamespacePagesDeploymentsMockData = {
}, },
}; };
export const getProjectPagesDeploymentsMockData = {
data: {
project: {
id: 'gid://gitlab/Project/19',
pagesDeployments: {
count: 3,
pageInfo: {
startCursor:
'eyJjcmVhdGVkX2F0IjoiMjAyNC0wNS0yMiAxMzozNzoyMi40MTk4MzcwMDAgKzAwMDAiLCJpZCI6IjQ4In0',
endCursor:
'eyJjcmVhdGVkX2F0IjoiMjAyNC0wNS0yMiAxMzozNzoyMi40MTk4MzcwMDAgKzAwMDAiLCJpZCI6IjQ2In0',
hasNextPage: false,
hasPreviousPage: false,
__typename: 'PageInfo',
},
nodes: [
{
id: 'gid://gitlab/PagesDeployment/48',
active: true,
ciBuildId: '499',
createdAt: '2024-05-22T13:37:22Z',
deletedAt: null,
expiresAt: null,
fileCount: 3,
pathPrefix: '_mr2019',
rootDirectory: 'public',
size: 1082,
updatedAt: '2024-08-07T13:14:10Z',
url: 'http://my-html-page-root-57991cf20198ae591a39bb7e54a451c8050c28335f427.pages.gdk.test:3010/_mr2019',
__typename: 'PagesDeployment',
},
{
id: 'gid://gitlab/PagesDeployment/47',
active: true,
ciBuildId: '499',
createdAt: '2024-05-22T13:37:22Z',
deletedAt: null,
expiresAt: null,
fileCount: 3,
pathPrefix: '_mr2018',
rootDirectory: 'public',
size: 1082,
updatedAt: '2024-08-07T13:14:21Z',
url: 'http://my-html-page-root-57991cf20198ae591a39bb7e54a451c8050c28335f427.pages.gdk.test:3010/_mr2018',
__typename: 'PagesDeployment',
},
{
id: 'gid://gitlab/PagesDeployment/46',
active: true,
ciBuildId: '499',
createdAt: '2024-05-22T13:37:22Z',
deletedAt: null,
expiresAt: null,
fileCount: 3,
pathPrefix: '_mr2017',
rootDirectory: 'public',
size: 1082,
updatedAt: '2024-08-07T13:14:26Z',
url: 'http://my-html-page-root-57991cf20198ae591a39bb7e54a451c8050c28335f427.pages.gdk.test:3010/_mr2017',
__typename: 'PagesDeployment',
},
],
__typename: 'PagesDeploymentConnection',
},
__typename: 'Project',
},
},
};
export const getEmptyNamespacePagesDeploymentsMockData = { export const getEmptyNamespacePagesDeploymentsMockData = {
data: { data: {
namespace: { namespace: {
...@@ -144,6 +213,27 @@ export const getEmptyNamespacePagesDeploymentsMockData = { ...@@ -144,6 +213,27 @@ export const getEmptyNamespacePagesDeploymentsMockData = {
}, },
}; };
export const getEmptyProjectPagesDeploymentsMockData = {
data: {
project: {
id: 'gid://gitlab/Project/19',
pagesDeployments: {
count: 0,
pageInfo: {
startCursor: null,
endCursor: null,
hasNextPage: false,
hasPreviousPage: false,
__typename: 'PageInfo',
},
nodes: [],
__typename: 'PagesDeploymentConnection',
},
__typename: 'Project',
},
},
};
export const mockError = { export const mockError = {
errors: [ errors: [
{ {
......
...@@ -4,12 +4,16 @@ import Vue from 'vue'; ...@@ -4,12 +4,16 @@ import Vue from 'vue';
import VueApollo from 'vue-apollo'; import VueApollo from 'vue-apollo';
import PagesProjects from 'ee/usage_quotas/pages/components/project_list.vue'; import PagesProjects from 'ee/usage_quotas/pages/components/project_list.vue';
import ProjectView from 'ee/usage_quotas/pages/components/project.vue'; import ProjectView from 'ee/usage_quotas/pages/components/project.vue';
import { GROUP_VIEW_TYPE, PROFILE_VIEW_TYPE, PROJECT_VIEW_TYPE } from '~/usage_quotas/constants';
import createMockApollo from 'helpers/mock_apollo_helper'; import createMockApollo from 'helpers/mock_apollo_helper';
import GetNamespacePagesDeployments from 'ee/usage_quotas/pages/graphql/pages_deployments.query.graphql'; import GetNamespacePagesDeployments from 'ee/usage_quotas/pages/graphql/pages_deployments.query.graphql';
import GetProjectPagesDeployments from '~/gitlab_pages/queries/get_project_pages_deployments.graphql';
import waitForPromises from 'helpers/wait_for_promises'; import waitForPromises from 'helpers/wait_for_promises';
import { import {
getNamespacePagesDeploymentsMockData, getNamespacePagesDeploymentsMockData,
getProjectPagesDeploymentsMockData,
getEmptyNamespacePagesDeploymentsMockData, getEmptyNamespacePagesDeploymentsMockData,
getEmptyProjectPagesDeploymentsMockData,
mockError, mockError,
} from './mock_data'; } from './mock_data';
...@@ -21,88 +25,155 @@ jest.mock( ...@@ -21,88 +25,155 @@ jest.mock(
Vue.use(VueApollo); Vue.use(VueApollo);
describe('PagesProjects', () => { describe('PagesProjects', () => {
const mockProjects = getNamespacePagesDeploymentsMockData.data.namespace.projects.nodes; const mockNamespaceProjects = getNamespacePagesDeploymentsMockData.data.namespace.projects.nodes;
const mockProject = getProjectPagesDeploymentsMockData.data.project;
const getNamespacePagesDeploymentsQueryHandler = jest
.fn()
.mockResolvedValue(getNamespacePagesDeploymentsMockData);
const getProjectPagesDeploymentsQueryHandler = jest
.fn()
.mockResolvedValue(getProjectPagesDeploymentsMockData);
const getAllHandlersMockedWithFn = (fn) => [
[GetNamespacePagesDeployments, fn],
[GetProjectPagesDeployments, fn],
];
const defaultHandler = [
[GetNamespacePagesDeployments, getNamespacePagesDeploymentsQueryHandler],
[GetProjectPagesDeployments, getProjectPagesDeploymentsQueryHandler],
];
const errorHandler = getAllHandlersMockedWithFn(jest.fn().mockRejectedValue(mockError));
const foreverLoadingHandler = getAllHandlersMockedWithFn(Promise);
const emptyResultsHandler = [
[
GetNamespacePagesDeployments,
jest.fn().mockResolvedValue(getEmptyNamespacePagesDeploymentsMockData),
],
[
GetProjectPagesDeployments,
jest.fn().mockResolvedValue(getEmptyProjectPagesDeploymentsMockData),
],
];
let wrapper; let wrapper;
let mockApollo; let mockApollo;
let viewType;
const createComponent = ( const createComponent = (handler = defaultHandler, props = {}) => {
queryHandler = jest.fn().mockResolvedValue(getNamespacePagesDeploymentsMockData), mockApollo = createMockApollo(handler);
props = {},
) => {
mockApollo = createMockApollo([[GetNamespacePagesDeployments, queryHandler]]);
return shallowMount(PagesProjects, { return shallowMount(PagesProjects, {
propsData: props, propsData: props,
provide: { provide: {
fullPath: 'test/path', fullPath: 'test/path',
viewType,
}, },
apolloProvider: mockApollo, apolloProvider: mockApollo,
}); });
}; };
it('calls the apollo query with the expected variables', () => { describe.each`
const handler = jest.fn(); view | expectedHandler
${GROUP_VIEW_TYPE} | ${getNamespacePagesDeploymentsQueryHandler}
${PROFILE_VIEW_TYPE} | ${getNamespacePagesDeploymentsQueryHandler}
${PROJECT_VIEW_TYPE} | ${getProjectPagesDeploymentsQueryHandler}
`(`in a $view`, ({ view, expectedHandler }) => {
beforeEach(() => {
viewType = view;
});
wrapper = createComponent(handler, { sort: 'UPDATED_ASC' }); it('calls the apollo query with the expected variables', () => {
wrapper = createComponent(defaultHandler, { sort: 'UPDATED_ASC' });
expect(handler).toHaveBeenCalledWith({ expect(expectedHandler).toHaveBeenCalledWith({
fullPath: 'test/path', fullPath: 'test/path',
first: 15, first: 15,
sort: 'UPDATED_ASC', sort: 'UPDATED_ASC',
active: true, active: true,
versioned: true, versioned: true,
});
}); });
});
it('renders loading icon while loading', () => { it('renders loading icon while loading', () => {
wrapper = createComponent(Promise); wrapper = createComponent(foreverLoadingHandler);
expect(wrapper.findComponent(GlLoadingIcon).exists()).toBe(true); expect(wrapper.findComponent(GlLoadingIcon).exists()).toBe(true);
}); });
it('renders project rows when there are results', async () => { it('does not show projects with no pages deployments', async () => {
wrapper = createComponent(); wrapper = createComponent();
await waitForPromises(); await waitForPromises();
const projectRows = wrapper.findAllComponents(ProjectView); const projectRows = wrapper.findAllComponents(ProjectView);
expect(projectRows).toHaveLength(2); expect(projectRows.wrappers.map((w) => w.props('project').id)).not.toContain(
expect(projectRows.at(0).props('project')).toEqual(mockProjects[0]); 'gid://gitlab/Project/3',
expect(projectRows.at(1).props('project')).toEqual(mockProjects[2]); );
}); });
it('does not show projects with no pages deployments', async () => { it('renders error alert when apollo has an error', async () => {
wrapper = createComponent(); wrapper = createComponent(errorHandler);
await waitForPromises(); await waitForPromises();
const projectRows = wrapper.findAllComponents(ProjectView); const alert = wrapper.findComponent(GlAlert);
expect(projectRows.wrappers.map((w) => w.props('project').id)).not.toContain( expect(alert.exists()).toBe(true);
'gid://gitlab/Project/3', expect(alert.props('variant')).toBe('danger');
); expect(alert.text()).toContain('An error occurred trying to load the Pages deployments.');
});
}); });
it('renders error alert when apollo has an error', async () => { describe.each([GROUP_VIEW_TYPE, PROFILE_VIEW_TYPE])('namespace view', (i, view) => {
wrapper = createComponent(jest.fn().mockRejectedValue(mockError)); beforeEach(() => {
viewType = view;
});
it('renders project rows when there are results', async () => {
wrapper = createComponent();
await waitForPromises(); await waitForPromises();
const alert = wrapper.findComponent(GlAlert); const projectRows = wrapper.findAllComponents(ProjectView);
expect(alert.exists()).toBe(true); expect(projectRows).toHaveLength(2);
expect(alert.props('variant')).toBe('danger'); expect(projectRows.at(0).props('project')).toEqual(mockNamespaceProjects[0]);
expect(alert.text()).toContain('An error occurred trying to load the Pages deployments.'); expect(projectRows.at(1).props('project')).toEqual(mockNamespaceProjects[2]);
});
it('renders empty state when the project list is empty', async () => {
wrapper = createComponent(emptyResultsHandler);
await waitForPromises();
const emptyState = wrapper.findComponent(GlEmptyState);
expect(emptyState.exists()).toBe(true);
expect(emptyState.props('title')).toBe('No projects found');
expect(emptyState.props('svgPath')).toBe('mocked-svg-url');
});
}); });
it('renders empty state when the project list is empty', async () => { describe('project view', () => {
wrapper = createComponent( beforeEach(() => {
jest.fn().mockResolvedValue(getEmptyNamespacePagesDeploymentsMockData), viewType = PROJECT_VIEW_TYPE;
); });
await waitForPromises(); it('renders project rows when there are results', async () => {
wrapper = createComponent();
const emptyState = wrapper.findComponent(GlEmptyState); await waitForPromises();
expect(emptyState.exists()).toBe(true);
expect(emptyState.props('title')).toBe('No projects found'); const projectRows = wrapper.findAllComponents(ProjectView);
expect(emptyState.props('svgPath')).toBe('mocked-svg-url'); expect(projectRows).toHaveLength(1);
expect(projectRows.at(0).props('project')).toEqual(mockProject);
});
it('renders empty state when the project list is empty', async () => {
wrapper = createComponent(emptyResultsHandler);
await waitForPromises();
const emptyState = wrapper.findComponent(GlEmptyState);
expect(emptyState.exists()).toBe(true);
expect(emptyState.props('title')).toBe('No parallel deployments');
expect(emptyState.props('svgPath')).toBe('mocked-svg-url');
});
}); });
}); });
...@@ -3,6 +3,7 @@ import { mountExtended } from 'helpers/vue_test_utils_helper'; ...@@ -3,6 +3,7 @@ import { mountExtended } from 'helpers/vue_test_utils_helper';
import ProjectView from 'ee/usage_quotas/pages/components/project.vue'; import ProjectView from 'ee/usage_quotas/pages/components/project.vue';
import UserDate from '~/vue_shared/components/user_date.vue'; import UserDate from '~/vue_shared/components/user_date.vue';
import NumberToHumanSize from '~/vue_shared/components/number_to_human_size/number_to_human_size.vue'; import NumberToHumanSize from '~/vue_shared/components/number_to_human_size/number_to_human_size.vue';
import { GROUP_VIEW_TYPE, PROFILE_VIEW_TYPE, PROJECT_VIEW_TYPE } from '~/usage_quotas/constants';
describe('ProjectView', () => { describe('ProjectView', () => {
let wrapper; let wrapper;
...@@ -41,84 +42,118 @@ describe('ProjectView', () => { ...@@ -41,84 +42,118 @@ describe('ProjectView', () => {
const findAllUrls = () => wrapper.findAllByTestId('url'); const findAllUrls = () => wrapper.findAllByTestId('url');
const findAllCiBuilds = () => wrapper.findAllByTestId('ci-build'); const findAllCiBuilds = () => wrapper.findAllByTestId('ci-build');
const findAvatar = () => wrapper.findComponent(GlAvatar); const findAvatar = () => wrapper.findComponent(GlAvatar);
const createComponent = (viewType) => {
beforeEach(() => {
wrapper = mountExtended(ProjectView, { wrapper = mountExtended(ProjectView, {
propsData: { propsData: {
project: mockProject, project: mockProject,
}, },
provide: {
viewType,
},
}); });
}); };
it('renders the project name and avatar', () => { describe.each([GROUP_VIEW_TYPE, PROFILE_VIEW_TYPE, PROJECT_VIEW_TYPE])(
const projectName = findProjectName(); 'with viewType=%s',
const avatar = findAvatar(); (_, viewType) => {
expect(projectName.text()).toContain('Test Project'); beforeEach(() => {
expect(avatar.props('src')).toBe('http://example.com/avatar.png'); createComponent(viewType);
expect(projectName.find('a').attributes('href')).toBe('/group/test-project/pages'); });
});
it('displays the correct number of total deployments', () => { it('renders the correct number of deployment rows', () => {
expect(wrapper.text().replace(/\s\s+/g, ' ')).toContain('Parallel deployments: 100'); expect(wrapper.findAll('.deployments-table tbody tr')).toHaveLength(2);
}); });
it('renders the correct number of deployment rows', () => { it('shows the correct state for active and inactive deployments', () => {
expect(wrapper.findAll('.deployments-table tbody tr')).toHaveLength(2); const badges = findAllStatusBadges();
}); expect(badges.at(0).text()).toContain('Active');
expect(badges.at(1).text()).toContain('Stopped');
});
it('shows the correct state for active and inactive deployments', () => { it('displays the correct path prefix for each deployment', () => {
const badges = findAllStatusBadges(); const pathPrefixes = findAllPathPrefixes();
expect(badges.at(0).text()).toContain('Active'); expect(pathPrefixes.at(0).text()).toContain('/foo');
expect(badges.at(1).text()).toContain('Stopped'); expect(pathPrefixes.at(1).text()).toContain('/bar');
}); });
it('displays the correct path prefix for each deployment', () => { it('renders active URLs as links and inactive URLs as text', () => {
const pathPrefixes = findAllPathPrefixes(); const urls = findAllUrls();
expect(pathPrefixes.at(0).text()).toContain('/foo'); expect(urls.at(0).element.tagName).toBe('A');
expect(pathPrefixes.at(1).text()).toContain('/bar'); expect(urls.at(0).attributes('href')).toBe('http://example.com/foo');
}); expect(urls.at(1).element.tagName).not.toBe('A');
});
it('renders active URLs as links and inactive URLs as text', () => { it('passes the creation date correctly to UserDate', () => {
const urls = findAllUrls(); const dates = wrapper.findAllComponents(UserDate);
expect(urls.at(0).element.tagName).toBe('A'); expect(dates.at(0).props('date')).toBe('2023-01-01T00:00:00Z');
expect(urls.at(0).attributes('href')).toBe('http://example.com/foo'); expect(dates.at(1).props('date')).toBe('2023-01-02T00:00:00Z');
expect(urls.at(1).element.tagName).not.toBe('A'); });
});
it('passes the creation date correctly to UserDate', () => { it('generates correct build URLs', () => {
const dates = wrapper.findAllComponents(UserDate); const buildLinks = findAllCiBuilds();
expect(dates.at(0).props('date')).toBe('2023-01-01T00:00:00Z'); expect(buildLinks.at(0).attributes('href')).toContain('/group/test-project/-/jobs/100');
expect(dates.at(1).props('date')).toBe('2023-01-02T00:00:00Z'); expect(buildLinks.at(1).attributes('href')).toContain('/group/test-project/-/jobs/101');
}); });
it('generates correct build URLs', () => { it('renders the size of deployments using NumberToHumanSize component', () => {
const buildLinks = findAllCiBuilds(); const sizes = wrapper.findAllComponents(NumberToHumanSize);
expect(buildLinks.at(0).attributes('href')).toContain('/group/test-project/-/jobs/100'); expect(sizes.at(0).props('value')).toBe(1024);
expect(buildLinks.at(1).attributes('href')).toContain('/group/test-project/-/jobs/101'); expect(sizes.at(1).props('value')).toBe(2048);
}); });
it('renders the size of deployments using NumberToHumanSize component', () => { it('shows "View all" link when there are more deployments', () => {
const sizes = wrapper.findAllComponents(NumberToHumanSize); expect(wrapper.text()).toContain('+ 98 more deployments');
expect(sizes.at(0).props('value')).toBe(1024); expect(wrapper.text()).toContain('View all');
expect(sizes.at(1).props('value')).toBe(2048); });
});
it('shows "View all" link when there are more deployments', () => { it('does not show "View all" link when all deployments are displayed', async () => {
expect(wrapper.text()).toContain('+ 98 more deployments'); await wrapper.setProps({
expect(wrapper.text()).toContain('View all'); project: {
...mockProject,
pagesDeployments: {
count: 2,
nodes: mockProject.pagesDeployments.nodes,
},
},
});
expect(wrapper.text()).not.toContain('more deployments');
expect(wrapper.text()).not.toContain('View all');
});
},
);
describe.each([GROUP_VIEW_TYPE, PROFILE_VIEW_TYPE])('namespace view', (_, viewType) => {
beforeEach(() => {
createComponent(viewType);
});
it('renders the project name and avatar', () => {
const projectName = findProjectName();
const avatar = findAvatar();
expect(projectName.text()).toContain('Test Project');
expect(avatar.props('src')).toBe('http://example.com/avatar.png');
expect(projectName.find('a').attributes('href')).toBe('/group/test-project/pages');
});
it('displays the correct number of total deployments', () => {
expect(wrapper.text().replace(/\s\s+/g, ' ')).toContain('Parallel deployments: 100');
});
}); });
it('does not show "View all" link when all deployments are displayed', async () => { describe('project view', () => {
await wrapper.setProps({ beforeEach(() => {
project: { createComponent(PROJECT_VIEW_TYPE);
...mockProject, });
pagesDeployments: {
count: 2, it('does not render the project name and avatar', () => {
nodes: mockProject.pagesDeployments.nodes, const projectName = findProjectName();
}, const avatar = findAvatar();
}, expect(avatar.exists()).toBe(false);
expect(projectName.exists()).toBe(false);
});
it('does not display the number of total deployments', () => {
expect(wrapper.text().replace(/\s\s+/g, ' ')).not.toContain('Parallel deployments');
}); });
expect(wrapper.text()).not.toContain('more deployments');
expect(wrapper.text()).not.toContain('View all');
}); });
}); });
...@@ -36930,6 +36930,9 @@ msgstr "" ...@@ -36930,6 +36930,9 @@ msgstr ""
msgid "No other labels with such name or description" msgid "No other labels with such name or description"
msgstr "" msgstr ""
   
msgid "No parallel deployments"
msgstr ""
msgid "No parent group" msgid "No parent group"
msgstr "" msgstr ""
   
...@@ -40075,6 +40078,9 @@ msgstr "" ...@@ -40075,6 +40078,9 @@ msgstr ""
msgid "Pages|Stopped" msgid "Pages|Stopped"
msgstr "" msgstr ""
   
msgid "Pages|There are no active parallel Pages deployments in this project."
msgstr ""
msgid "Pages|There was an error trying to delete the deployment" msgid "Pages|There was an error trying to delete the deployment"
msgstr "" msgstr ""
   
0% 加载中 .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册