Commit a9f9b885 authored by Olena Horal-Koretska's avatar Olena Horal-Koretska

Merge branch '347409-migrate-package-details-page-to-vue-router-4' into 'master'

Enable frontend routing for package details

See merge request gitlab-org/gitlab!77762
parents c940f3d7 37277b57
...@@ -4,6 +4,7 @@ import { parseBoolean } from '~/lib/utils/common_utils'; ...@@ -4,6 +4,7 @@ import { parseBoolean } from '~/lib/utils/common_utils';
import PerformancePlugin from '~/performance/vue_performance_plugin'; import PerformancePlugin from '~/performance/vue_performance_plugin';
import Translate from '~/vue_shared/translate'; import Translate from '~/vue_shared/translate';
import RegistryBreadcrumb from '~/packages_and_registries/shared/components/registry_breadcrumb.vue'; import RegistryBreadcrumb from '~/packages_and_registries/shared/components/registry_breadcrumb.vue';
import { renderBreadcrumb } from '~/packages_and_registries/shared/utils';
import { apolloProvider } from './graphql/index'; import { apolloProvider } from './graphql/index';
import RegistryExplorer from './pages/index.vue'; import RegistryExplorer from './pages/index.vue';
import createRouter from './router'; import createRouter from './router';
...@@ -84,38 +85,8 @@ export default () => { ...@@ -84,38 +85,8 @@ export default () => {
}, },
}); });
const attachBreadcrumb = () => { return {
const breadCrumbEls = document.querySelectorAll('nav .js-breadcrumbs-list li'); attachBreadcrumb: renderBreadcrumb(router, apolloProvider, RegistryBreadcrumb),
const breadCrumbEl = breadCrumbEls[breadCrumbEls.length - 1]; attachMainComponent,
const crumbs = [breadCrumbEl.querySelector('h2')];
const nestedBreadcrumbEl = document.createElement('div');
breadCrumbEl.replaceChild(nestedBreadcrumbEl, breadCrumbEl.querySelector('h2'));
return new Vue({
el: nestedBreadcrumbEl,
router,
apolloProvider,
components: {
RegistryBreadcrumb,
},
render(createElement) {
// FIXME(@tnir): this is a workaround until the MR gets merged:
// https://gitlab.com/gitlab-org/gitlab/-/merge_requests/48115
const parentEl = breadCrumbEl.parentElement.parentElement;
if (parentEl) {
parentEl.classList.remove('breadcrumbs-container');
parentEl.classList.add('gl-display-flex');
parentEl.classList.add('w-100');
}
// End of FIXME(@tnir)
return createElement('registry-breadcrumb', {
class: breadCrumbEl.className,
props: {
crumbs,
},
});
},
});
}; };
return { attachBreadcrumb, attachMainComponent };
}; };
...@@ -26,7 +26,7 @@ export default { ...@@ -26,7 +26,7 @@ export default {
GlSprintf, GlSprintf,
GlFormRadioGroup, GlFormRadioGroup,
}, },
inject: ['npmPath'], inject: ['npmInstanceUrl'],
props: { props: {
packageEntity: { packageEntity: {
type: Object, type: Object,
...@@ -66,7 +66,9 @@ export default { ...@@ -66,7 +66,9 @@ export default {
npmSetupCommand(type, endpointType) { npmSetupCommand(type, endpointType) {
const scope = this.packageEntity.name.substring(0, this.packageEntity.name.indexOf('/')); const scope = this.packageEntity.name.substring(0, this.packageEntity.name.indexOf('/'));
const npmPathForEndpoint = const npmPathForEndpoint =
endpointType === INSTANCE_PACKAGE_ENDPOINT_TYPE ? this.npmPath : this.packageEntity.npmUrl; endpointType === INSTANCE_PACKAGE_ENDPOINT_TYPE
? this.npmInstanceUrl
: this.packageEntity.npmUrl;
if (type === NPM_PACKAGE_MANAGER) { if (type === NPM_PACKAGE_MANAGER) {
return `echo ${scope}:registry=${npmPathForEndpoint}/ >> .npmrc`; return `echo ${scope}:registry=${npmPathForEndpoint}/ >> .npmrc`;
......
<script> <script>
import { GlButton, GlLink, GlSprintf, GlTooltipDirective, GlTruncate } from '@gitlab/ui'; import { GlButton, GlSprintf, GlTooltipDirective, GlTruncate } from '@gitlab/ui';
import { s__, __ } from '~/locale'; import { s__, __ } from '~/locale';
import ListItem from '~/vue_shared/components/registry/list_item.vue'; import ListItem from '~/vue_shared/components/registry/list_item.vue';
import { import {
...@@ -18,7 +18,6 @@ export default { ...@@ -18,7 +18,6 @@ export default {
name: 'PackageListRow', name: 'PackageListRow',
components: { components: {
GlButton, GlButton,
GlLink,
GlSprintf, GlSprintf,
GlTruncate, GlTruncate,
PackageTags, PackageTags,
...@@ -42,9 +41,8 @@ export default { ...@@ -42,9 +41,8 @@ export default {
packageType() { packageType() {
return getPackageTypeLabel(this.packageEntity.packageType); return getPackageTypeLabel(this.packageEntity.packageType);
}, },
packageLink() { packageId() {
const { project, id } = this.packageEntity; return getIdFromGraphQLId(this.packageEntity.id);
return `${project?.webUrl}/-/packages/${getIdFromGraphQLId(id)}`;
}, },
pipeline() { pipeline() {
return this.packageEntity?.pipelines?.nodes[0]; return this.packageEntity?.pipelines?.nodes[0];
...@@ -61,6 +59,9 @@ export default { ...@@ -61,6 +59,9 @@ export default {
disabledRow() { disabledRow() {
return this.packageEntity.status && this.packageEntity.status !== PACKAGE_DEFAULT_STATUS; return this.packageEntity.status && this.packageEntity.status !== PACKAGE_DEFAULT_STATUS;
}, },
routerLinkEvent() {
return this.disabledRow ? '' : 'click';
},
}, },
i18n: { i18n: {
erroredPackageText: s__('PackageRegistry|Invalid Package: failed metadata extraction'), erroredPackageText: s__('PackageRegistry|Invalid Package: failed metadata extraction'),
...@@ -73,14 +74,15 @@ export default { ...@@ -73,14 +74,15 @@ export default {
<list-item data-qa-selector="package_row" :disabled="disabledRow"> <list-item data-qa-selector="package_row" :disabled="disabledRow">
<template #left-primary> <template #left-primary>
<div class="gl-display-flex gl-align-items-center gl-mr-3 gl-min-w-0"> <div class="gl-display-flex gl-align-items-center gl-mr-3 gl-min-w-0">
<gl-link <router-link
:href="packageLink"
class="gl-text-body gl-min-w-0" class="gl-text-body gl-min-w-0"
data-testid="details-link"
data-qa-selector="package_link" data-qa-selector="package_link"
:disabled="disabledRow" :event="routerLinkEvent"
:to="{ name: 'details', params: { id: packageId } }"
> >
<gl-truncate :text="packageEntity.name" /> <gl-truncate :text="packageEntity.name" />
</gl-link> </router-link>
<gl-button <gl-button
v-if="showWarningIcon" v-if="showWarningIcon"
......
...@@ -74,6 +74,7 @@ export const FETCH_PACKAGE_DETAILS_ERROR_MESSAGE = s__( ...@@ -74,6 +74,7 @@ export const FETCH_PACKAGE_DETAILS_ERROR_MESSAGE = s__(
); );
export const DELETE_PACKAGE_SUCCESS_MESSAGE = s__('PackageRegistry|Package deleted successfully'); export const DELETE_PACKAGE_SUCCESS_MESSAGE = s__('PackageRegistry|Package deleted successfully');
export const PACKAGE_REGISTRY_TITLE = __('Package Registry');
export const PACKAGE_ERROR_STATUS = 'ERROR'; export const PACKAGE_ERROR_STATUS = 'ERROR';
export const PACKAGE_DEFAULT_STATUS = 'DEFAULT'; export const PACKAGE_DEFAULT_STATUS = 'DEFAULT';
......
...@@ -2,29 +2,59 @@ import Vue from 'vue'; ...@@ -2,29 +2,59 @@ import Vue from 'vue';
import Translate from '~/vue_shared/translate'; import Translate from '~/vue_shared/translate';
import { apolloProvider } from '~/packages_and_registries/package_registry/graphql/index'; import { apolloProvider } from '~/packages_and_registries/package_registry/graphql/index';
import PackageRegistry from '~/packages_and_registries/package_registry/pages/index.vue'; import PackageRegistry from '~/packages_and_registries/package_registry/pages/index.vue';
import RegistryBreadcrumb from '~/packages_and_registries/shared/components/registry_breadcrumb.vue';
import { renderBreadcrumb } from '~/packages_and_registries/shared/utils';
import createRouter from './router'; import createRouter from './router';
Vue.use(Translate); Vue.use(Translate);
export default () => { export default () => {
const el = document.getElementById('js-vue-packages-list'); const el = document.getElementById('js-vue-packages-list');
const { endpoint, resourceId, fullPath, pageType, emptyListIllustration } = el.dataset; const {
const router = createRouter(endpoint); endpoint,
resourceId,
fullPath,
pageType,
emptyListIllustration,
npmInstanceUrl,
projectListUrl,
groupListUrl,
} = el.dataset;
const isGroupPage = pageType === 'groups'; const isGroupPage = pageType === 'groups';
return new Vue({ // This is a mini state to help the breadcrumb have the correct name in the details page
el, const breadCrumbState = Vue.observable({
router, name: '',
apolloProvider, updateName(value) {
provide: { this.name = value;
resourceId,
fullPath,
emptyListIllustration,
isGroupPage,
},
render(createElement) {
return createElement(PackageRegistry);
}, },
}); });
const router = createRouter(endpoint, breadCrumbState);
const attachMainComponent = () =>
new Vue({
el,
router,
apolloProvider,
provide: {
resourceId,
fullPath,
emptyListIllustration,
isGroupPage,
npmInstanceUrl,
projectListUrl,
groupListUrl,
breadCrumbState,
},
render(createElement) {
return createElement(PackageRegistry);
},
});
return {
attachBreadcrumb: renderBreadcrumb(router, apolloProvider, RegistryBreadcrumb),
attachMainComponent,
};
}; };
import Vue from 'vue';
import { parseBoolean } from '~/lib/utils/common_utils';
import PackagesApp from '~/packages_and_registries/package_registry/components/details/app.vue';
import { apolloProvider } from '~/packages_and_registries/package_registry/graphql/index';
import Translate from '~/vue_shared/translate';
Vue.use(Translate);
export default () => {
const el = document.getElementById('js-vue-packages-detail-new');
if (!el) {
return null;
}
const { canDelete, ...datasetOptions } = el.dataset;
return new Vue({
el,
apolloProvider,
provide: {
canDelete: parseBoolean(canDelete),
...datasetOptions,
},
render(createElement) {
return createElement(PackagesApp);
},
});
};
...@@ -68,7 +68,7 @@ export default { ...@@ -68,7 +68,7 @@ export default {
GlModal: GlModalDirective, GlModal: GlModalDirective,
}, },
mixins: [Tracking.mixin()], mixins: [Tracking.mixin()],
inject: ['packageId', 'svgPath', 'projectListUrl', 'groupListUrl'], inject: ['emptyListIllustration', 'projectListUrl', 'groupListUrl', 'breadCrumbState'],
trackingActions: { trackingActions: {
DELETE_PACKAGE_TRACKING_ACTION, DELETE_PACKAGE_TRACKING_ACTION,
REQUEST_DELETE_PACKAGE_TRACKING_ACTION, REQUEST_DELETE_PACKAGE_TRACKING_ACTION,
...@@ -100,12 +100,20 @@ export default { ...@@ -100,12 +100,20 @@ export default {
error, error,
}); });
}, },
result() {
this.breadCrumbState.updateName(
`${this.packageEntity?.name} v ${this.packageEntity?.version}`,
);
},
}, },
}, },
computed: { computed: {
projectName() { projectName() {
return this.packageEntity.project?.name; return this.packageEntity.project?.name;
}, },
packageId() {
return this.$route.params.id;
},
queryVariables() { queryVariables() {
return { return {
id: convertToGraphQLId('Packages::Package', this.packageId), id: convertToGraphQLId('Packages::Package', this.packageId),
...@@ -229,7 +237,7 @@ export default { ...@@ -229,7 +237,7 @@ export default {
v-if="!isValidPackage" v-if="!isValidPackage"
:title="s__('PackageRegistry|Unable to load package')" :title="s__('PackageRegistry|Unable to load package')"
:description="s__('PackageRegistry|There was a problem fetching the details for this package.')" :description="s__('PackageRegistry|There was a problem fetching the details for this package.')"
:svg-path="svgPath" :svg-path="emptyListIllustration"
/> />
<div v-else-if="!isLoading" class="packages-app"> <div v-else-if="!isLoading" class="packages-app">
<package-title :package-entity="packageEntity"> <package-title :package-entity="packageEntity">
......
import Vue from 'vue'; import Vue from 'vue';
import VueRouter from 'vue-router'; import VueRouter from 'vue-router';
import List from '~/packages_and_registries/package_registry/pages/list.vue'; import List from '~/packages_and_registries/package_registry/pages/list.vue';
import Details from '~/packages_and_registries/package_registry/pages/details.vue';
import { PACKAGE_REGISTRY_TITLE } from '~/packages_and_registries/package_registry/constants';
Vue.use(VueRouter); Vue.use(VueRouter);
export default function createRouter(base) { export default function createRouter(base, breadCrumbState) {
const router = new VueRouter({ const router = new VueRouter({
base, base,
mode: 'history', mode: 'history',
...@@ -13,9 +15,25 @@ export default function createRouter(base) { ...@@ -13,9 +15,25 @@ export default function createRouter(base) {
name: 'list', name: 'list',
path: '/', path: '/',
component: List, component: List,
meta: {
nameGenerator: () => PACKAGE_REGISTRY_TITLE,
root: true,
},
},
{
name: 'details',
path: '/:id',
component: Details,
meta: {
nameGenerator: () => breadCrumbState.name,
},
}, },
], ],
}); });
router.afterEach(() => {
breadCrumbState.updateName('');
});
return router; return router;
} }
...@@ -20,8 +20,11 @@ export default { ...@@ -20,8 +20,11 @@ export default {
isRootRoute() { isRootRoute() {
return this.$route.name === this.rootRoute.name; return this.$route.name === this.rootRoute.name;
}, },
detailsRouteName() {
return this.detailsRoute.meta.nameGenerator();
},
isLoaded() { isLoaded() {
return this.isRootRoute || this.$store?.state.imageDetails?.name; return this.isRootRoute || this.detailsRouteName;
}, },
allCrumbs() { allCrumbs() {
const crumbs = [ const crumbs = [
...@@ -32,7 +35,7 @@ export default { ...@@ -32,7 +35,7 @@ export default {
]; ];
if (!this.isRootRoute) { if (!this.isRootRoute) {
crumbs.push({ crumbs.push({
text: this.detailsRoute.meta.nameGenerator(), text: this.detailsRouteName,
href: this.detailsRoute.meta.path, href: this.detailsRoute.meta.path,
}); });
} }
...@@ -45,7 +48,9 @@ export default { ...@@ -45,7 +48,9 @@ export default {
<template> <template>
<gl-breadcrumb :key="isLoaded" :items="allCrumbs"> <gl-breadcrumb :key="isLoaded" :items="allCrumbs">
<template #separator> <template #separator>
<gl-icon name="angle-right" :size="8" /> <span class="gl-mx-n5">
<gl-icon name="angle-right" :size="8" />
</span>
</template> </template>
</gl-breadcrumb> </gl-breadcrumb>
</template> </template>
import Vue from 'vue';
import { queryToObject } from '~/lib/utils/url_utility'; import { queryToObject } from '~/lib/utils/url_utility';
import { FILTERED_SEARCH_TERM } from './constants'; import { FILTERED_SEARCH_TERM } from './constants';
...@@ -38,3 +39,37 @@ export const getCommitLink = ({ project_path: projectPath, pipeline = {} }, isGr ...@@ -38,3 +39,37 @@ export const getCommitLink = ({ project_path: projectPath, pipeline = {} }, isGr
return `../commit/${pipeline.sha}`; return `../commit/${pipeline.sha}`;
}; };
export const renderBreadcrumb = (router, apolloProvider, RegistryBreadcrumb) => () => {
const breadCrumbEls = document.querySelectorAll('nav .js-breadcrumbs-list li');
const breadCrumbEl = breadCrumbEls[breadCrumbEls.length - 1];
const lastCrumb = breadCrumbEl.children[0];
const crumbs = [lastCrumb];
const nestedBreadcrumbEl = document.createElement('div');
breadCrumbEl.replaceChild(nestedBreadcrumbEl, lastCrumb);
return new Vue({
el: nestedBreadcrumbEl,
router,
apolloProvider,
components: {
RegistryBreadcrumb,
},
render(createElement) {
// FIXME(@tnir): this is a workaround until the MR gets merged:
// https://gitlab.com/gitlab-org/gitlab/-/merge_requests/48115
const parentEl = breadCrumbEl.parentElement.parentElement;
if (parentEl) {
parentEl.classList.remove('breadcrumbs-container');
parentEl.classList.add('gl-display-flex');
parentEl.classList.add('w-100');
}
// End of FIXME(@tnir)
return createElement('registry-breadcrumb', {
class: breadCrumbEl.className,
props: {
crumbs,
},
});
},
});
};
import packageApp from '~/packages_and_registries/package_registry/index'; import packageApp from '~/packages_and_registries/package_registry/index';
packageApp(); const app = packageApp();
if (app) {
app.attachBreadcrumb();
app.attachMainComponent();
}
import packageApp from '~/packages_and_registries/package_registry/index'; import packageApp from '~/packages_and_registries/package_registry/index';
packageApp(); const app = packageApp();
if (app) {
app.attachBreadcrumb();
app.attachMainComponent();
}
import initPackageDetails from '~/packages_and_registries/package_registry/pages/details';
initPackageDetails();
...@@ -6,6 +6,11 @@ module Groups ...@@ -6,6 +6,11 @@ module Groups
feature_category :package_registry feature_category :package_registry
# The show action renders index to allow frontend routing to work on page refresh
def show
render :index
end
private private
def verify_packages_enabled! def verify_packages_enabled!
......
...@@ -7,8 +7,9 @@ module Projects ...@@ -7,8 +7,9 @@ module Projects
feature_category :package_registry feature_category :package_registry
# The show action renders index to allow frontend routing to work on page refresh
def show def show
@package = project.packages.find(params[:id]) render :index
end end
end end
end end
......
...@@ -7,4 +7,7 @@ ...@@ -7,4 +7,7 @@
full_path: @group.full_path, full_path: @group.full_path,
endpoint: group_packages_path(@group), endpoint: group_packages_path(@group),
page_type: 'groups', page_type: 'groups',
empty_list_illustration: image_path('illustrations/no-packages.svg'), } } empty_list_illustration: image_path('illustrations/no-packages.svg'),
npm_instance_url: package_registry_instance_url(:npm),
project_list_url: '',
group_list_url: group_packages_path(@group) } }
...@@ -7,4 +7,7 @@ ...@@ -7,4 +7,7 @@
full_path: @project.full_path, full_path: @project.full_path,
endpoint: project_packages_path(@project), endpoint: project_packages_path(@project),
page_type: 'projects', page_type: 'projects',
empty_list_illustration: image_path('illustrations/no-packages.svg'), } } empty_list_illustration: image_path('illustrations/no-packages.svg'),
npm_instance_url: package_registry_instance_url(:npm),
project_list_url: project_packages_path(@project),
group_list_url: '' } }
- add_to_breadcrumbs _("Package Registry"), project_packages_path(@project)
- add_to_breadcrumbs @package.name, project_packages_path(@project)
- breadcrumb_title @package.version
- page_title _("Package Registry")
- @content_class = "limit-container-width" unless fluid_layout
.row
.col-12
#js-vue-packages-detail-new{ data: package_details_data(@project, @package) }
...@@ -64,7 +64,7 @@ constraints(::Constraints::GroupUrlConstrainer.new) do ...@@ -64,7 +64,7 @@ constraints(::Constraints::GroupUrlConstrainer.new) do
post :toggle_subscription, on: :member post :toggle_subscription, on: :member
end end
resources :packages, only: [:index] resources :packages, only: [:index, :show]
resources :milestones, constraints: { id: %r{[^/]+} } do resources :milestones, constraints: { id: %r{[^/]+} } do
member do member do
......
...@@ -5,7 +5,7 @@ module QA ...@@ -5,7 +5,7 @@ module QA
module Project module Project
module Packages module Packages
class Show < QA::Page::Base class Show < QA::Page::Base
view 'app/assets/javascripts/packages_and_registries/package_registry/components/details/app.vue' do view 'app/assets/javascripts/packages_and_registries/package_registry/pages/details.vue' do
element :delete_button element :delete_button
element :delete_modal_button element :delete_modal_button
element :package_information_content element :package_information_content
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Groups::PackagesController do
let_it_be(:group) { create(:group) }
let(:page) { :index }
let(:additional_parameters) { {} }
subject do
get page, params: additional_parameters.merge({
group_id: group
})
end
context 'GET #index' do
it_behaves_like 'returning response status', :ok
end
context 'GET #show' do
let(:page) { :show }
let(:additional_parameters) { { id: 1 } }
it_behaves_like 'returning response status', :ok
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Projects::Packages::PackagesController do
let_it_be(:project) { create(:project, :public) }
let(:page) { :index }
let(:additional_parameters) { {} }
subject do
get page, params: additional_parameters.merge({
project_id: project,
namespace_id: project.namespace
})
end
context 'GET #index' do
it_behaves_like 'returning response status', :ok
end
context 'GET #show' do
let(:page) { :show }
let(:additional_parameters) { { id: 1 } }
it_behaves_like 'returning response status', :ok
end
end
...@@ -42,6 +42,9 @@ RSpec.describe 'Group Packages' do ...@@ -42,6 +42,9 @@ RSpec.describe 'Group Packages' do
let_it_be(:maven_package) { create(:maven_package, project: second_project, name: 'aaa', created_at: 2.days.ago, version: '2.0.0') } let_it_be(:maven_package) { create(:maven_package, project: second_project, name: 'aaa', created_at: 2.days.ago, version: '2.0.0') }
let_it_be(:packages) { [npm_package, maven_package] } let_it_be(:packages) { [npm_package, maven_package] }
let(:package) { packages.first }
let(:package_details_path) { group_package_path(group, package) }
it_behaves_like 'packages list', check_project_name: true it_behaves_like 'packages list', check_project_name: true
it_behaves_like 'package details link' it_behaves_like 'package details link'
......
...@@ -35,6 +35,9 @@ RSpec.describe 'Packages' do ...@@ -35,6 +35,9 @@ RSpec.describe 'Packages' do
let_it_be(:maven_package) { create(:maven_package, project: project, name: 'aaa', created_at: 2.days.ago, version: '2.0.0') } let_it_be(:maven_package) { create(:maven_package, project: project, name: 'aaa', created_at: 2.days.ago, version: '2.0.0') }
let_it_be(:packages) { [npm_package, maven_package] } let_it_be(:packages) { [npm_package, maven_package] }
let(:package) { packages.first }
let(:package_details_path) { project_package_path(project, package) }
it_behaves_like 'packages list' it_behaves_like 'packages list'
it_behaves_like 'package details link' it_behaves_like 'package details link'
......
...@@ -32,7 +32,7 @@ exports[`NpmInstallation renders all the messages 1`] = ` ...@@ -32,7 +32,7 @@ exports[`NpmInstallation renders all the messages 1`] = `
<code-instruction-stub <code-instruction-stub
copytext="Copy npm setup command" copytext="Copy npm setup command"
instruction="echo @gitlab-org:registry=npmPath/ >> .npmrc" instruction="echo @gitlab-org:registry=npmInstanceUrl/ >> .npmrc"
label="" label=""
trackingaction="copy_npm_setup_command" trackingaction="copy_npm_setup_command"
trackinglabel="code_instruction" trackinglabel="code_instruction"
......
...@@ -35,7 +35,7 @@ describe('NpmInstallation', () => { ...@@ -35,7 +35,7 @@ describe('NpmInstallation', () => {
function createComponent({ data = {} } = {}) { function createComponent({ data = {} } = {}) {
wrapper = shallowMountExtended(NpmInstallation, { wrapper = shallowMountExtended(NpmInstallation, {
provide: { provide: {
npmPath: 'npmPath', npmInstanceUrl: 'npmInstanceUrl',
}, },
propsData: { propsData: {
packageEntity, packageEntity,
...@@ -117,7 +117,7 @@ describe('NpmInstallation', () => { ...@@ -117,7 +117,7 @@ describe('NpmInstallation', () => {
it('renders the correct setup command', () => { it('renders the correct setup command', () => {
expect(findCodeInstructions().at(1).props()).toMatchObject({ expect(findCodeInstructions().at(1).props()).toMatchObject({
instruction: 'echo @gitlab-org:registry=npmPath/ >> .npmrc', instruction: 'echo @gitlab-org:registry=npmInstanceUrl/ >> .npmrc',
multiline: false, multiline: false,
trackingAction: TRACKING_ACTION_COPY_NPM_SETUP_COMMAND, trackingAction: TRACKING_ACTION_COPY_NPM_SETUP_COMMAND,
}); });
...@@ -139,7 +139,7 @@ describe('NpmInstallation', () => { ...@@ -139,7 +139,7 @@ describe('NpmInstallation', () => {
await nextTick(); await nextTick();
expect(findCodeInstructions().at(1).props()).toMatchObject({ expect(findCodeInstructions().at(1).props()).toMatchObject({
instruction: `echo @gitlab-org:registry=npmPath/ >> .npmrc`, instruction: `echo @gitlab-org:registry=npmInstanceUrl/ >> .npmrc`,
multiline: false, multiline: false,
trackingAction: TRACKING_ACTION_COPY_NPM_SETUP_COMMAND, trackingAction: TRACKING_ACTION_COPY_NPM_SETUP_COMMAND,
}); });
...@@ -161,7 +161,7 @@ describe('NpmInstallation', () => { ...@@ -161,7 +161,7 @@ describe('NpmInstallation', () => {
it('renders the correct registry command', () => { it('renders the correct registry command', () => {
expect(findCodeInstructions().at(1).props()).toMatchObject({ expect(findCodeInstructions().at(1).props()).toMatchObject({
instruction: 'echo \\"@gitlab-org:registry\\" \\"npmPath/\\" >> .yarnrc', instruction: 'echo \\"@gitlab-org:registry\\" \\"npmInstanceUrl/\\" >> .yarnrc',
multiline: false, multiline: false,
trackingAction: TRACKING_ACTION_COPY_YARN_SETUP_COMMAND, trackingAction: TRACKING_ACTION_COPY_YARN_SETUP_COMMAND,
}); });
...@@ -183,7 +183,7 @@ describe('NpmInstallation', () => { ...@@ -183,7 +183,7 @@ describe('NpmInstallation', () => {
await nextTick(); await nextTick();
expect(findCodeInstructions().at(1).props()).toMatchObject({ expect(findCodeInstructions().at(1).props()).toMatchObject({
instruction: 'echo \\"@gitlab-org:registry\\" \\"npmPath/\\" >> .yarnrc', instruction: 'echo \\"@gitlab-org:registry\\" \\"npmInstanceUrl/\\" >> .yarnrc',
multiline: false, multiline: false,
trackingAction: TRACKING_ACTION_COPY_YARN_SETUP_COMMAND, trackingAction: TRACKING_ACTION_COPY_YARN_SETUP_COMMAND,
}); });
......
...@@ -22,16 +22,20 @@ exports[`packages_list_row renders 1`] = ` ...@@ -22,16 +22,20 @@ exports[`packages_list_row renders 1`] = `
<div <div
class="gl-display-flex gl-align-items-center gl-mr-3 gl-min-w-0" class="gl-display-flex gl-align-items-center gl-mr-3 gl-min-w-0"
> >
<gl-link-stub <router-link-stub
ariacurrentvalue="page"
class="gl-text-body gl-min-w-0" class="gl-text-body gl-min-w-0"
data-qa-selector="package_link" data-qa-selector="package_link"
href="http://gdk.test:3000/gitlab-org/gitlab-test/-/packages/111" data-testid="details-link"
event="click"
tag="a"
to="[object Object]"
> >
<gl-truncate-stub <gl-truncate-stub
position="end" position="end"
text="@gitlab-org/package-15" text="@gitlab-org/package-15"
/> />
</gl-link-stub> </router-link-stub>
<!----> <!---->
......
import { GlLink, GlSprintf } from '@gitlab/ui'; import { GlSprintf } from '@gitlab/ui';
import { createLocalVue } from '@vue/test-utils';
import VueRouter from 'vue-router';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive'; import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import PackagesListRow from '~/packages_and_registries/package_registry/components/list/package_list_row.vue'; import PackagesListRow from '~/packages_and_registries/package_registry/components/list/package_list_row.vue';
import PackagePath from '~/packages_and_registries/shared/components/package_path.vue'; import PackagePath from '~/packages_and_registries/shared/components/package_path.vue';
import PackageTags from '~/packages_and_registries/shared/components/package_tags.vue'; import PackageTags from '~/packages_and_registries/shared/components/package_tags.vue';
...@@ -13,6 +17,9 @@ import { PACKAGE_ERROR_STATUS } from '~/packages_and_registries/package_registry ...@@ -13,6 +17,9 @@ import { PACKAGE_ERROR_STATUS } from '~/packages_and_registries/package_registry
import ListItem from '~/vue_shared/components/registry/list_item.vue'; import ListItem from '~/vue_shared/components/registry/list_item.vue';
import { packageData, packagePipelines, packageProject, packageTags } from '../../mock_data'; import { packageData, packagePipelines, packageProject, packageTags } from '../../mock_data';
const localVue = createLocalVue();
localVue.use(VueRouter);
describe('packages_list_row', () => { describe('packages_list_row', () => {
let wrapper; let wrapper;
...@@ -28,7 +35,7 @@ describe('packages_list_row', () => { ...@@ -28,7 +35,7 @@ describe('packages_list_row', () => {
const findDeleteButton = () => wrapper.findByTestId('action-delete'); const findDeleteButton = () => wrapper.findByTestId('action-delete');
const findPackageIconAndName = () => wrapper.find(PackageIconAndName); const findPackageIconAndName = () => wrapper.find(PackageIconAndName);
const findListItem = () => wrapper.findComponent(ListItem); const findListItem = () => wrapper.findComponent(ListItem);
const findPackageLink = () => wrapper.findComponent(GlLink); const findPackageLink = () => wrapper.findByTestId('details-link');
const findWarningIcon = () => wrapper.findByTestId('warning-icon'); const findWarningIcon = () => wrapper.findByTestId('warning-icon');
const findLeftSecondaryInfos = () => wrapper.findByTestId('left-secondary-infos'); const findLeftSecondaryInfos = () => wrapper.findByTestId('left-secondary-infos');
const findPublishMethod = () => wrapper.findComponent(PublishMethod); const findPublishMethod = () => wrapper.findComponent(PublishMethod);
...@@ -40,6 +47,7 @@ describe('packages_list_row', () => { ...@@ -40,6 +47,7 @@ describe('packages_list_row', () => {
provide = defaultProvide, provide = defaultProvide,
} = {}) => { } = {}) => {
wrapper = shallowMountExtended(PackagesListRow, { wrapper = shallowMountExtended(PackagesListRow, {
localVue,
provide, provide,
stubs: { stubs: {
ListItem, ListItem,
...@@ -63,6 +71,15 @@ describe('packages_list_row', () => { ...@@ -63,6 +71,15 @@ describe('packages_list_row', () => {
expect(wrapper.element).toMatchSnapshot(); expect(wrapper.element).toMatchSnapshot();
}); });
it('has a link to navigate to the details page', () => {
mountComponent();
expect(findPackageLink().props()).toMatchObject({
event: 'click',
to: { name: 'details', params: { id: getIdFromGraphQLId(packageWithoutTags.id) } },
});
});
describe('tags', () => { describe('tags', () => {
it('renders package tags when a package has tags', () => { it('renders package tags when a package has tags', () => {
mountComponent({ packageEntity: packageWithTags }); mountComponent({ packageEntity: packageWithTags });
...@@ -120,7 +137,7 @@ describe('packages_list_row', () => { ...@@ -120,7 +137,7 @@ describe('packages_list_row', () => {
}); });
it('details link is disabled', () => { it('details link is disabled', () => {
expect(findPackageLink().attributes('disabled')).toBe('true'); expect(findPackageLink().props('event')).toBe('');
}); });
it('has a warning icon', () => { it('has a warning icon', () => {
......
...@@ -9,7 +9,7 @@ import waitForPromises from 'helpers/wait_for_promises'; ...@@ -9,7 +9,7 @@ import waitForPromises from 'helpers/wait_for_promises';
import createFlash from '~/flash'; import createFlash from '~/flash';
import AdditionalMetadata from '~/packages_and_registries/package_registry/components/details/additional_metadata.vue'; import AdditionalMetadata from '~/packages_and_registries/package_registry/components/details/additional_metadata.vue';
import PackagesApp from '~/packages_and_registries/package_registry/components/details/app.vue'; import PackagesApp from '~/packages_and_registries/package_registry/pages/details.vue';
import DependencyRow from '~/packages_and_registries/package_registry/components/details/dependency_row.vue'; import DependencyRow from '~/packages_and_registries/package_registry/components/details/dependency_row.vue';
import InstallationCommands from '~/packages_and_registries/package_registry/components/details/installation_commands.vue'; import InstallationCommands from '~/packages_and_registries/package_registry/components/details/installation_commands.vue';
import PackageFiles from '~/packages_and_registries/package_registry/components/details/package_files.vue'; import PackageFiles from '~/packages_and_registries/package_registry/components/details/package_files.vue';
...@@ -36,7 +36,7 @@ import { ...@@ -36,7 +36,7 @@ import {
packageFiles, packageFiles,
packageDestroyFileMutation, packageDestroyFileMutation,
packageDestroyFileMutationError, packageDestroyFileMutationError,
} from '../../mock_data'; } from '../mock_data';
jest.mock('~/flash'); jest.mock('~/flash');
useMockLocationHelper(); useMockLocationHelper();
...@@ -47,18 +47,22 @@ describe('PackagesApp', () => { ...@@ -47,18 +47,22 @@ describe('PackagesApp', () => {
let wrapper; let wrapper;
let apolloProvider; let apolloProvider;
const breadCrumbState = {
updateName: jest.fn(),
};
const provide = { const provide = {
packageId: '111', packageId: '111',
svgPath: 'svgPath', emptyListIllustration: 'svgPath',
npmPath: 'npmPath',
npmHelpPath: 'npmHelpPath',
projectListUrl: 'projectListUrl', projectListUrl: 'projectListUrl',
groupListUrl: 'groupListUrl', groupListUrl: 'groupListUrl',
breadCrumbState,
}; };
function createComponent({ function createComponent({
resolver = jest.fn().mockResolvedValue(packageDetailsQuery()), resolver = jest.fn().mockResolvedValue(packageDetailsQuery()),
fileDeleteMutationResolver = jest.fn().mockResolvedValue(packageDestroyFileMutation()), fileDeleteMutationResolver = jest.fn().mockResolvedValue(packageDestroyFileMutation()),
routeId = '1',
} = {}) { } = {}) {
localVue.use(VueApollo); localVue.use(VueApollo);
...@@ -84,6 +88,13 @@ describe('PackagesApp', () => { ...@@ -84,6 +88,13 @@ describe('PackagesApp', () => {
GlTabs, GlTabs,
GlTab, GlTab,
}, },
mocks: {
$route: {
params: {
id: routeId,
},
},
},
}); });
} }
...@@ -172,6 +183,15 @@ describe('PackagesApp', () => { ...@@ -172,6 +183,15 @@ describe('PackagesApp', () => {
}); });
}); });
it('calls the appropriate function to set the breadcrumbState', async () => {
const { name, version } = packageData();
createComponent();
await waitForPromises();
expect(breadCrumbState.updateName).toHaveBeenCalledWith(`${name} v ${version}`);
});
describe('delete package', () => { describe('delete package', () => {
const originalReferrer = document.referrer; const originalReferrer = document.referrer;
const setReferrer = (value = packageDetailsQuery().data.package.project.name) => { const setReferrer = (value = packageDetailsQuery().data.package.project.name) => {
......
...@@ -24,16 +24,20 @@ exports[`Registry Breadcrumb when is not rootRoute renders 1`] = ` ...@@ -24,16 +24,20 @@ exports[`Registry Breadcrumb when is not rootRoute renders 1`] = `
class="gl-breadcrumb-separator" class="gl-breadcrumb-separator"
data-testid="separator" data-testid="separator"
> >
<svg <span
aria-hidden="true" class="gl-mx-n5"
class="gl-icon s8"
data-testid="angle-right-icon"
role="img"
> >
<use <svg
href="#angle-right" aria-hidden="true"
/> class="gl-icon s8"
</svg> data-testid="angle-right-icon"
role="img"
>
<use
href="#angle-right"
/>
</svg>
</span>
</span> </span>
</a> </a>
</li> </li>
......
...@@ -19,14 +19,12 @@ RSpec.shared_examples 'packages list' do |check_project_name: false| ...@@ -19,14 +19,12 @@ RSpec.shared_examples 'packages list' do |check_project_name: false|
end end
RSpec.shared_examples 'package details link' do |property| RSpec.shared_examples 'package details link' do |property|
let(:package) { packages.first }
it 'navigates to the correct url' do it 'navigates to the correct url' do
page.within(packages_table_selector) do page.within(packages_table_selector) do
click_link package.name click_link package.name
end end
expect(page).to have_current_path(project_package_path(package.project, package)) expect(page).to have_current_path(package_details_path)
expect(page).to have_css('.packages-app h2[data-testid="title"]', text: package.name) expect(page).to have_css('.packages-app h2[data-testid="title"]', text: package.name)
......
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