diff --git a/app/assets/javascripts/packages_and_registries/harbor_registry/components/harbor_registry_breadcrumb.vue b/app/assets/javascripts/packages_and_registries/harbor_registry/components/harbor_registry_breadcrumb.vue index 27fa26fd567faa29305bf7f49475ff0f267d9e6d..fae705a16e90a79c85edce0b8194719ca16d0886 100644 --- a/app/assets/javascripts/packages_and_registries/harbor_registry/components/harbor_registry_breadcrumb.vue +++ b/app/assets/javascripts/packages_and_registries/harbor_registry/components/harbor_registry_breadcrumb.vue @@ -9,6 +9,13 @@ export default { components: { GlBreadcrumb, }, + props: { + staticBreadcrumbs: { + type: Object, + default: () => ({ items: [] }), + required: false, + }, + }, computed: { rootRoute() { return this.$router.options.routes.find((r) => r.meta.root); @@ -35,7 +42,9 @@ export default { }); } - return routeInfoList; + const staticCrumbs = this.staticBreadcrumbs.items; + + return [...staticCrumbs, ...routeInfoList]; }, isLoaded() { return this.isRootRoute || last(this.currentRoute).text; @@ -50,7 +59,9 @@ export default { if (!this.isRootRoute) { crumbs = crumbs.concat(this.currentRoute); } - return crumbs; + const staticCrumbs = this.staticBreadcrumbs.items; + + return [...staticCrumbs, ...crumbs]; }, }, }; diff --git a/app/assets/javascripts/packages_and_registries/harbor_registry/index.js b/app/assets/javascripts/packages_and_registries/harbor_registry/index.js index 18187a5c5ff0d789442bc3c5483db8193036f17a..d3815da2e002a36609aad414b266f17f99ec104f 100644 --- a/app/assets/javascripts/packages_and_registries/harbor_registry/index.js +++ b/app/assets/javascripts/packages_and_registries/harbor_registry/index.js @@ -77,7 +77,17 @@ export default (id) => { }; return { - attachBreadcrumb: () => injectVueAppBreadcrumbs(router, RegistryBreadcrumb), + attachBreadcrumb: () => + injectVueAppBreadcrumbs( + router, + RegistryBreadcrumb, + null, + {}, + { + // cf. https://gitlab.com/gitlab-org/gitlab/-/merge_requests/186906 + singleNavOptIn: true, + }, + ), attachMainComponent, }; }; diff --git a/spec/frontend/packages_and_registries/harbor_registry/components/harbor_registry_breadcrumb.spec.js b/spec/frontend/packages_and_registries/harbor_registry/components/harbor_registry_breadcrumb.spec.js new file mode 100644 index 0000000000000000000000000000000000000000..94f4baf8e96ac78985c9d9c550b632f0cb0cc37b --- /dev/null +++ b/spec/frontend/packages_and_registries/harbor_registry/components/harbor_registry_breadcrumb.spec.js @@ -0,0 +1,93 @@ +import { shallowMount } from '@vue/test-utils'; +import { GlBreadcrumb } from '@gitlab/ui'; +import HarborRegistryBreadcrumb from '~/packages_and_registries/harbor_registry/components/harbor_registry_breadcrumb.vue'; + +describe('HarborRegistryBreadcrumb', () => { + let wrapper; + + const findBreadcrumbs = () => wrapper.findComponent(GlBreadcrumb); + + const rootRoute = { + name: 'root', + path: '/', + meta: { + root: true, + nameGenerator: () => 'Root', + hrefGenerator: () => '/', + }, + }; + + const detailsRoute = { + name: 'details', + path: '/details', + meta: { + nameGenerator: () => 'Details', + hrefGenerator: () => '/details', + }, + }; + + const createComponent = ({ route, routes, props = {} }) => { + wrapper = shallowMount(HarborRegistryBreadcrumb, { + propsData: props, + mocks: { + $route: route, + $router: { options: { routes } }, + }, + stubs: { + GlBreadcrumb: { + name: 'GlBreadcrumb', + props: ['items', 'autoResize'], + template: '', + }, + }, + }); + }; + + describe('when mounted', () => { + it('renders the root breadcrumb when on root route', () => { + createComponent({ + route: { name: 'root', meta: rootRoute.meta }, + routes: [rootRoute, detailsRoute], + }); + expect(findBreadcrumbs().props('items')).toStrictEqual([{ text: 'Root', to: '/' }]); + }); + + it('renders both root and current route breadcrumbs when not on root', () => { + createComponent({ + route: { name: 'details', meta: detailsRoute.meta }, + routes: [rootRoute, detailsRoute], + }); + expect(findBreadcrumbs().props('items')).toStrictEqual([ + { text: 'Root', to: '/' }, + { text: 'Details', to: '/details' }, + ]); + }); + }); + + describe('when static breadcrumbs are provided', () => { + it('renders static breadcrumbs along with route breadcrumbs', () => { + const staticBreadcrumbs = { + items: [{ text: 'Static Item', href: '/static' }], + }; + createComponent({ + route: { name: 'details', meta: detailsRoute.meta }, + routes: [rootRoute, detailsRoute], + props: { staticBreadcrumbs }, + }); + const items = findBreadcrumbs().props('items'); + expect(items[0]).toEqual(staticBreadcrumbs.items[0]); + }); + + it('handles empty static breadcrumbs', () => { + createComponent({ + route: { name: 'details', meta: detailsRoute.meta }, + routes: [rootRoute, detailsRoute], + props: { staticBreadcrumbs: { items: [] } }, + }); + expect(findBreadcrumbs().props('items')).toStrictEqual([ + { text: 'Root', to: '/' }, + { text: 'Details', to: '/details' }, + ]); + }); + }); +});