diff --git a/ee/app/assets/javascripts/usage_quotas/pages/components/project.vue b/ee/app/assets/javascripts/usage_quotas/pages/components/project.vue index 712f7f3bd7ff1b325b47781c3be6ca56664ffef1..9ce0995c83aa7d7856ce4f118e3827761afaeda6 100644 --- a/ee/app/assets/javascripts/usage_quotas/pages/components/project.vue +++ b/ee/app/assets/javascripts/usage_quotas/pages/components/project.vue @@ -2,6 +2,7 @@ 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 { 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 { joinPaths } from '~/lib/utils/url_utility'; import { s__, sprintf } from '~/locale'; @@ -21,6 +22,7 @@ export default { GlLink, GlAvatar, }, + inject: ['viewType'], props: { project: { type: Object, @@ -28,6 +30,7 @@ export default { }, }, static: { + PROJECT_VIEW_TYPE, SHORT_DATE_FORMAT_WITH_TIME, }, i18n: { @@ -85,6 +88,9 @@ export default { }, ], computed: { + isSingleProjectView() { + return this.viewType === this.$options.static.PROJECT_VIEW_TYPE; + }, pagesUrl() { return joinPaths(gon.relative_url_root || '', '/', this.project.fullPath, 'pages'); }, @@ -115,7 +121,7 @@ export default { <template> <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"> <gl-link :href="pagesUrl" class="gl-flex gl-items-center gl-no-underline"> <gl-avatar diff --git a/ee/app/assets/javascripts/usage_quotas/pages/components/project_list.vue b/ee/app/assets/javascripts/usage_quotas/pages/components/project_list.vue index 9df110e716e6c6213c7aac808dbecb52b3c5555e..6b7c091281db02495e79154ea76bb3f67863d410 100644 --- a/ee/app/assets/javascripts/usage_quotas/pages/components/project_list.vue +++ b/ee/app/assets/javascripts/usage_quotas/pages/components/project_list.vue @@ -1,6 +1,8 @@ <script> 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 { 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 ProjectView from './project.vue'; @@ -13,7 +15,8 @@ export default { GlAlert, }, EMPTY_STATE_SVG_URL, - inject: ['fullPath'], + PROJECT_VIEW_TYPE, + inject: ['fullPath', 'viewType'], props: { sort: { type: String, @@ -23,14 +26,36 @@ export default { }, data() { return { + project: null, projects: {}, resultsPerPage: 15, error: null, }; }, 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: { query: GetNamespacePagesDeployments, + skip() { + return this.isProjectView; + }, variables() { return { fullPath: this.fullPath, @@ -51,7 +76,13 @@ export default { }, }, computed: { + isProjectView() { + return this.viewType === this.$options.PROJECT_VIEW_TYPE; + }, hasResults() { + if (this.isProjectView) { + return this.project.pagesDeployments.nodes?.length; + } return this.projects?.length; }, }, @@ -65,15 +96,22 @@ export default { {{ s__('Pages|An error occurred trying to load the Pages deployments.') }} </gl-alert> <gl-empty-state - v-else-if="!hasResults" + v-else-if="!isProjectView && !hasResults" :title="__('No projects found')" :description=" s__('Pages|We did not find any projects with parallel Pages deployments in this namespace.') " :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"> - <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> </template> diff --git a/ee/app/assets/javascripts/usage_quotas/pages/tab_metadata.js b/ee/app/assets/javascripts/usage_quotas/pages/tab_metadata.js index 3a14707419fb57a9967ef75af4fe02fea8315e9f..341d36c47818e8ffe476b22b75be2106277fffa6 100644 --- a/ee/app/assets/javascripts/usage_quotas/pages/tab_metadata.js +++ b/ee/app/assets/javascripts/usage_quotas/pages/tab_metadata.js @@ -13,7 +13,7 @@ export const parseProvideData = (el) => { }; }; -export const getPagesTabMetadata = () => { +export const getPagesTabMetadata = ({ viewType } = {}) => { const el = document.querySelector(PAGES_TAB_METADATA_EL_SELECTOR); if (!el) return false; @@ -25,7 +25,10 @@ export const getPagesTabMetadata = () => { component: { name: 'PagesDeploymentsTab', apolloProvider, - provide: parseProvideData(el), + provide: { + viewType, + ...parseProvideData(el), + }, render(createElement) { return createElement(PagesDeploymentsApp); }, diff --git a/ee/app/assets/javascripts/usage_quotas/project_view_metadata.js b/ee/app/assets/javascripts/usage_quotas/project_view_metadata.js index f59c8c0ecf7881a1c197f3485dcd3b256016a8d3..637dac03fdc6805daff3128f66a9ee73a8e260fb 100644 --- a/ee/app/assets/javascripts/usage_quotas/project_view_metadata.js +++ b/ee/app/assets/javascripts/usage_quotas/project_view_metadata.js @@ -2,9 +2,11 @@ import { PROJECT_VIEW_TYPE } from '~/usage_quotas/constants'; import { getStorageTabMetadata } from '~/usage_quotas/storage/tab_metadata'; import { getTransferTabMetadata } from './transfer/tab_metadata'; import { getObservabilityTabMetadata } from './observability/tab_metadata'; +import { getPagesTabMetadata } from './pages/tab_metadata'; export const usageQuotasTabsMetadata = [ getStorageTabMetadata({ viewType: PROJECT_VIEW_TYPE }), getTransferTabMetadata({ viewType: PROJECT_VIEW_TYPE }), getObservabilityTabMetadata(), + getPagesTabMetadata({ viewType: PROJECT_VIEW_TYPE }), ].filter(Boolean); diff --git a/ee/spec/frontend/usage_quotas/pages/components/mock_data.js b/ee/spec/frontend/usage_quotas/pages/components/mock_data.js index 3bdc83ecd0e8abe51bf5a8ccaa85d700dc1b01ce..522d326bfc88c759093c25164a2e3f588d5a165f 100644 --- a/ee/spec/frontend/usage_quotas/pages/components/mock_data.js +++ b/ee/spec/frontend/usage_quotas/pages/components/mock_data.js @@ -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 = { data: { namespace: { @@ -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 = { errors: [ { diff --git a/ee/spec/frontend/usage_quotas/pages/components/project_list_spec.js b/ee/spec/frontend/usage_quotas/pages/components/project_list_spec.js index af937d60abefdb702eb833b2cdd37f5a8efc8c74..0437bdebaa7c19fb5c667dfaa12eb18c9c6cd9aa 100644 --- a/ee/spec/frontend/usage_quotas/pages/components/project_list_spec.js +++ b/ee/spec/frontend/usage_quotas/pages/components/project_list_spec.js @@ -4,12 +4,16 @@ import Vue from 'vue'; import VueApollo from 'vue-apollo'; import PagesProjects from 'ee/usage_quotas/pages/components/project_list.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 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 { getNamespacePagesDeploymentsMockData, + getProjectPagesDeploymentsMockData, getEmptyNamespacePagesDeploymentsMockData, + getEmptyProjectPagesDeploymentsMockData, mockError, } from './mock_data'; @@ -21,88 +25,155 @@ jest.mock( Vue.use(VueApollo); 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 mockApollo; + let viewType; - const createComponent = ( - queryHandler = jest.fn().mockResolvedValue(getNamespacePagesDeploymentsMockData), - props = {}, - ) => { - mockApollo = createMockApollo([[GetNamespacePagesDeployments, queryHandler]]); + const createComponent = (handler = defaultHandler, props = {}) => { + mockApollo = createMockApollo(handler); return shallowMount(PagesProjects, { propsData: props, provide: { fullPath: 'test/path', + viewType, }, apolloProvider: mockApollo, }); }; - it('calls the apollo query with the expected variables', () => { - const handler = jest.fn(); + describe.each` + 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({ - fullPath: 'test/path', - first: 15, - sort: 'UPDATED_ASC', - active: true, - versioned: true, + expect(expectedHandler).toHaveBeenCalledWith({ + fullPath: 'test/path', + first: 15, + sort: 'UPDATED_ASC', + active: true, + versioned: true, + }); }); - }); - it('renders loading icon while loading', () => { - wrapper = createComponent(Promise); + it('renders loading icon while loading', () => { + 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 () => { - wrapper = createComponent(); + it('does not show projects with no pages deployments', async () => { + wrapper = createComponent(); - await waitForPromises(); + await waitForPromises(); - const projectRows = wrapper.findAllComponents(ProjectView); - expect(projectRows).toHaveLength(2); - expect(projectRows.at(0).props('project')).toEqual(mockProjects[0]); - expect(projectRows.at(1).props('project')).toEqual(mockProjects[2]); - }); + const projectRows = wrapper.findAllComponents(ProjectView); + expect(projectRows.wrappers.map((w) => w.props('project').id)).not.toContain( + 'gid://gitlab/Project/3', + ); + }); - it('does not show projects with no pages deployments', async () => { - wrapper = createComponent(); + it('renders error alert when apollo has an error', async () => { + wrapper = createComponent(errorHandler); - await waitForPromises(); + await waitForPromises(); - const projectRows = wrapper.findAllComponents(ProjectView); - expect(projectRows.wrappers.map((w) => w.props('project').id)).not.toContain( - 'gid://gitlab/Project/3', - ); + const alert = wrapper.findComponent(GlAlert); + expect(alert.exists()).toBe(true); + 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 () => { - wrapper = createComponent(jest.fn().mockRejectedValue(mockError)); + describe.each([GROUP_VIEW_TYPE, PROFILE_VIEW_TYPE])('namespace view', (i, view) => { + beforeEach(() => { + viewType = view; + }); + + it('renders project rows when there are results', async () => { + wrapper = createComponent(); - await waitForPromises(); + await waitForPromises(); - const alert = wrapper.findComponent(GlAlert); - expect(alert.exists()).toBe(true); - expect(alert.props('variant')).toBe('danger'); - expect(alert.text()).toContain('An error occurred trying to load the Pages deployments.'); + const projectRows = wrapper.findAllComponents(ProjectView); + expect(projectRows).toHaveLength(2); + expect(projectRows.at(0).props('project')).toEqual(mockNamespaceProjects[0]); + 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 () => { - wrapper = createComponent( - jest.fn().mockResolvedValue(getEmptyNamespacePagesDeploymentsMockData), - ); + describe('project view', () => { + beforeEach(() => { + viewType = PROJECT_VIEW_TYPE; + }); - await waitForPromises(); + it('renders project rows when there are results', async () => { + wrapper = createComponent(); - 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'); + await waitForPromises(); + + const projectRows = wrapper.findAllComponents(ProjectView); + 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'); + }); }); }); diff --git a/ee/spec/frontend/usage_quotas/pages/components/project_spec.js b/ee/spec/frontend/usage_quotas/pages/components/project_spec.js index b9a9ba5cce8cf42a43c02dcd147c251bdf6fd7cf..3143dc0b7e8d2c25492baa954601be2d31a8f5b7 100644 --- a/ee/spec/frontend/usage_quotas/pages/components/project_spec.js +++ b/ee/spec/frontend/usage_quotas/pages/components/project_spec.js @@ -3,6 +3,7 @@ import { mountExtended } from 'helpers/vue_test_utils_helper'; import ProjectView from 'ee/usage_quotas/pages/components/project.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 { GROUP_VIEW_TYPE, PROFILE_VIEW_TYPE, PROJECT_VIEW_TYPE } from '~/usage_quotas/constants'; describe('ProjectView', () => { let wrapper; @@ -41,84 +42,118 @@ describe('ProjectView', () => { const findAllUrls = () => wrapper.findAllByTestId('url'); const findAllCiBuilds = () => wrapper.findAllByTestId('ci-build'); const findAvatar = () => wrapper.findComponent(GlAvatar); - - beforeEach(() => { + const createComponent = (viewType) => { wrapper = mountExtended(ProjectView, { propsData: { project: mockProject, }, + provide: { + 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'); - }); + describe.each([GROUP_VIEW_TYPE, PROFILE_VIEW_TYPE, PROJECT_VIEW_TYPE])( + 'with viewType=%s', + (_, viewType) => { + beforeEach(() => { + createComponent(viewType); + }); - it('displays the correct number of total deployments', () => { - expect(wrapper.text().replace(/\s\s+/g, ' ')).toContain('Parallel deployments: 100'); - }); + it('renders the correct number of deployment rows', () => { + expect(wrapper.findAll('.deployments-table tbody tr')).toHaveLength(2); + }); - it('renders the correct number of deployment rows', () => { - expect(wrapper.findAll('.deployments-table tbody tr')).toHaveLength(2); - }); + it('shows the correct state for active and inactive deployments', () => { + 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', () => { - const badges = findAllStatusBadges(); - expect(badges.at(0).text()).toContain('Active'); - expect(badges.at(1).text()).toContain('Stopped'); - }); + it('displays the correct path prefix for each deployment', () => { + const pathPrefixes = findAllPathPrefixes(); + expect(pathPrefixes.at(0).text()).toContain('/foo'); + expect(pathPrefixes.at(1).text()).toContain('/bar'); + }); - it('displays the correct path prefix for each deployment', () => { - const pathPrefixes = findAllPathPrefixes(); - expect(pathPrefixes.at(0).text()).toContain('/foo'); - expect(pathPrefixes.at(1).text()).toContain('/bar'); - }); + it('renders active URLs as links and inactive URLs as text', () => { + const urls = findAllUrls(); + expect(urls.at(0).element.tagName).toBe('A'); + 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', () => { - const urls = findAllUrls(); - expect(urls.at(0).element.tagName).toBe('A'); - expect(urls.at(0).attributes('href')).toBe('http://example.com/foo'); - expect(urls.at(1).element.tagName).not.toBe('A'); - }); + it('passes the creation date correctly to UserDate', () => { + const dates = wrapper.findAllComponents(UserDate); + expect(dates.at(0).props('date')).toBe('2023-01-01T00:00:00Z'); + expect(dates.at(1).props('date')).toBe('2023-01-02T00:00:00Z'); + }); - it('passes the creation date correctly to UserDate', () => { - const dates = wrapper.findAllComponents(UserDate); - expect(dates.at(0).props('date')).toBe('2023-01-01T00:00:00Z'); - expect(dates.at(1).props('date')).toBe('2023-01-02T00:00:00Z'); - }); + it('generates correct build URLs', () => { + const buildLinks = findAllCiBuilds(); + expect(buildLinks.at(0).attributes('href')).toContain('/group/test-project/-/jobs/100'); + expect(buildLinks.at(1).attributes('href')).toContain('/group/test-project/-/jobs/101'); + }); - it('generates correct build URLs', () => { - const buildLinks = findAllCiBuilds(); - expect(buildLinks.at(0).attributes('href')).toContain('/group/test-project/-/jobs/100'); - expect(buildLinks.at(1).attributes('href')).toContain('/group/test-project/-/jobs/101'); - }); + it('renders the size of deployments using NumberToHumanSize component', () => { + const sizes = wrapper.findAllComponents(NumberToHumanSize); + expect(sizes.at(0).props('value')).toBe(1024); + expect(sizes.at(1).props('value')).toBe(2048); + }); - it('renders the size of deployments using NumberToHumanSize component', () => { - const sizes = wrapper.findAllComponents(NumberToHumanSize); - expect(sizes.at(0).props('value')).toBe(1024); - expect(sizes.at(1).props('value')).toBe(2048); - }); + it('shows "View all" link when there are more deployments', () => { + expect(wrapper.text()).toContain('+ 98 more deployments'); + expect(wrapper.text()).toContain('View all'); + }); - it('shows "View all" link when there are more deployments', () => { - expect(wrapper.text()).toContain('+ 98 more deployments'); - expect(wrapper.text()).toContain('View all'); + it('does not show "View all" link when all deployments are displayed', async () => { + await wrapper.setProps({ + 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 () => { - await wrapper.setProps({ - project: { - ...mockProject, - pagesDeployments: { - count: 2, - nodes: mockProject.pagesDeployments.nodes, - }, - }, + describe('project view', () => { + beforeEach(() => { + createComponent(PROJECT_VIEW_TYPE); + }); + + it('does not render the project name and avatar', () => { + 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'); }); }); diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 71f864d9ba27324d0a0958e4a5309ea53f7897ad..5e73f1e4ecbb046141935b6df440b14535238396 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -36930,6 +36930,9 @@ msgstr "" msgid "No other labels with such name or description" msgstr "" +msgid "No parallel deployments" +msgstr "" + msgid "No parent group" msgstr "" @@ -40075,6 +40078,9 @@ msgstr "" msgid "Pages|Stopped" 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" msgstr ""