From c3566e03e0d2c3d9c174744b3674b98c85e681e3 Mon Sep 17 00:00:00 2001 From: Evgeniya Vashkevich Date: Fri, 27 Sep 2024 11:58:42 -0700 Subject: [PATCH] Vue3: Fixed navigation active style not applied (#12015) --- .../side-nav/product-side-nav.spec.ts | 34 +++++++ shell/components/nav/Type.vue | 22 ++++- shell/components/nav/__tests__/Type.test.ts | 92 ++++++++++++++----- 3 files changed, 123 insertions(+), 25 deletions(-) diff --git a/cypress/e2e/tests/navigation/side-nav/product-side-nav.spec.ts b/cypress/e2e/tests/navigation/side-nav/product-side-nav.spec.ts index 02b7d37af35..b3aa67a781e 100644 --- a/cypress/e2e/tests/navigation/side-nav/product-side-nav.spec.ts +++ b/cypress/e2e/tests/navigation/side-nav/product-side-nav.spec.ts @@ -2,9 +2,21 @@ import HomePagePo from '@/cypress/e2e/po/pages/home.po'; import BurgerMenuPo from '@/cypress/e2e/po/side-bars/burger-side-menu.po'; import ProductNavPo from '@/cypress/e2e/po/side-bars/product-side-nav.po'; import WorkloadPagePo from '@/cypress/e2e/po/pages/explorer/workloads.po'; +import { WorkloadsDeploymentsListPagePo } from '@/cypress/e2e/po/pages/explorer/workloads/workloads-deployments.po'; +import { createDeploymentBlueprint } from '@/cypress/e2e/blueprints/explorer/workloads/deployments/deployment-create'; + +const { name: workloadName, namespace } = createDeploymentBlueprint.metadata; +const deploymentsListPage = new WorkloadsDeploymentsListPagePo('local'); Cypress.config(); describe('Side navigation: Cluster ', { tags: ['@navigation', '@adminUser'] }, () => { + before(() => { + cy.login(); + cy.intercept('GET', `/v1/apps.deployments/${ namespace }/${ workloadName }`).as('testWorkload'); + + deploymentsListPage.goTo(); + deploymentsListPage.createWithKubectl(createDeploymentBlueprint); + }); beforeEach(() => { cy.login(); @@ -52,6 +64,21 @@ describe('Side navigation: Cluster ', { tags: ['@navigation', '@adminUser'] }, ( cy.get('@closedGroup').find('.router-link-active').should('have.length.gt', 0); }); + it('Going into resource detail should keep relevant group active', () => { + const productNavPo = new ProductNavPo(); + + productNavPo.groups().get('.expanded').as('openGroup'); + + productNavPo.visibleNavTypes().eq(1).should('be.visible').click(); // Go into Workloads + const workload = new WorkloadPagePo('local'); + + workload.goTo(); + workload.waitForPage(); + workload.goToDetailsPage(workloadName); + cy.get('@openGroup').should('be.visible'); + cy.get('@openGroup').find('.router-link-active').should('have.length.gt', 0); + }); + it('Should access to every navigation provided from the server link, including nested cases, without errors', () => { const productNavPo = new ProductNavPo(); // iterate through top-level groups @@ -97,4 +124,11 @@ describe('Side navigation: Cluster ', { tags: ['@navigation', '@adminUser'] }, ( productNavPo.tabHeaders().eq(1).click(1, 1).then(() => cy.url().should('equal', workloadsUrl)); }); }); + + after(() => { + cy.login(); + deploymentsListPage?.goTo(); + + deploymentsListPage.deleteWithKubectl(workloadName, namespace); + }); }); diff --git a/shell/components/nav/Type.vue b/shell/components/nav/Type.vue index f7c55627a35..d9d28a27b10 100644 --- a/shell/components/nav/Type.vue +++ b/shell/components/nav/Type.vue @@ -54,6 +54,26 @@ export default { const inStore = this.$store.getters['currentStore'](this.type.name); return this.$store.getters[`${ inStore }/count`]({ name: this.type.name }); + }, + + isActive() { + const typeFullPath = this.$router.resolve(this.type.route)?.fullPath.toLowerCase(); + const pageFullPath = this.$route.fullPath?.toLowerCase(); + + if ( !this.type.exact) { + const typeSplit = typeFullPath.split('/'); + const pageSplit = pageFullPath.split('/'); + + for (let index = 0; index < typeSplit.length; ++index) { + if ( index >= pageSplit.length || typeSplit[index] !== pageSplit[index] ) { + return false; + } + } + + return true; + } + + return typeFullPath === pageFullPath; } }, @@ -81,7 +101,7 @@ export default { diff --git a/shell/components/nav/__tests__/Type.test.ts b/shell/components/nav/__tests__/Type.test.ts index a9a6bce89fd..c69e4405468 100644 --- a/shell/components/nav/__tests__/Type.test.ts +++ b/shell/components/nav/__tests__/Type.test.ts @@ -2,7 +2,7 @@ import { nextTick } from 'vue'; import { shallowMount, RouterLinkStub } from '@vue/test-utils'; import Type from '@shell/components/nav/Type.vue'; import { createChildRenderingRouterLinkStub } from '@shell/utils/unit-tests/ChildRenderingRouterLinkStub'; -import { TYPE_MODES } from '@shell/store/type-map'; +import { TYPE_MODES } from '@shell/store/type-map.js'; // Configuration const activeClass = 'router-link-active'; @@ -25,6 +25,12 @@ describe('component: Type', () => { 'cluster/count': () => defaultCount, } }; + const routerMock = { + resolve: jest.fn((route) => { + return { fullPath: route }; + }) + }; + const routeMock = { fullPath: 'route' }; describe('should pass props correctly', () => { it('should forward Type props to router-link', () => { @@ -33,8 +39,10 @@ describe('component: Type', () => { global: { directives: { cleanHtml: (identity) => identity }, - mocks: { $store: storeMock }, - stubs: { routerLink: RouterLinkStub }, + mocks: { + $store: storeMock, $router: routerMock, $route: routeMock + }, + stubs: { routerLink: RouterLinkStub }, }, }); @@ -51,7 +59,9 @@ describe('component: Type', () => { global: { directives: { cleanHtml: (identity) => identity }, - mocks: { $store: storeMock }, + mocks: { + $store: storeMock, $router: routerMock, $route: routeMock + }, stubs: { routerLink: createChildRenderingRouterLinkStub({ href: fakeHref }) }, }, }); @@ -69,8 +79,10 @@ describe('component: Type', () => { global: { directives: { cleanHtml: (identity) => identity }, - mocks: { $store: storeMock }, - stubs: { routerLink: createChildRenderingRouterLinkStub({ isActive: true, navigate }) }, + mocks: { + $store: storeMock, $router: routerMock, $route: routeMock + }, + stubs: { routerLink: createChildRenderingRouterLinkStub({ navigate }) }, }, }); @@ -90,8 +102,10 @@ describe('component: Type', () => { global: { directives: { cleanHtml: (identity) => identity }, - mocks: { $store: storeMock }, - stubs: { routerLink: createChildRenderingRouterLinkStub({ isActive: false }) }, + mocks: { + $store: storeMock, $router: routerMock, $route: { fullPath: 'bad' } + }, + stubs: { routerLink: createChildRenderingRouterLinkStub() }, }, }); @@ -107,7 +121,9 @@ describe('component: Type', () => { global: { directives: { cleanHtml: (identity) => identity }, - mocks: { $store: storeMock }, + mocks: { + $store: storeMock, $router: routerMock, $route: routeMock + }, stubs: { routerLink: createChildRenderingRouterLinkStub({ isExactActive: false }) }, }, }); @@ -124,7 +140,9 @@ describe('component: Type', () => { global: { directives: { cleanHtml: (identity) => identity }, - mocks: { $store: storeMock }, + mocks: { + $store: storeMock, $router: routerMock, $route: routeMock + }, stubs: { routerLink: createChildRenderingRouterLinkStub() }, }, }); @@ -143,8 +161,10 @@ describe('component: Type', () => { global: { directives: { cleanHtml: (identity) => identity }, - mocks: { $store: storeMock }, - stubs: { routerLink: createChildRenderingRouterLinkStub({ isActive: true }) }, + mocks: { + $store: storeMock, $router: routerMock, $route: routeMock + }, + stubs: { routerLink: createChildRenderingRouterLinkStub() }, }, }); @@ -160,7 +180,9 @@ describe('component: Type', () => { global: { directives: { cleanHtml: (identity) => identity }, - mocks: { $store: storeMock }, + mocks: { + $store: storeMock, $router: routerMock, $route: routeMock + }, stubs: { routerLink: createChildRenderingRouterLinkStub({ isExactActive: true }) }, }, }); @@ -177,7 +199,9 @@ describe('component: Type', () => { global: { directives: { cleanHtml: (identity) => identity }, - mocks: { $store: storeMock }, + mocks: { + $store: storeMock, $router: routerMock, $route: routeMock + }, stubs: { routerLink: createChildRenderingRouterLinkStub() }, }, }); @@ -194,7 +218,9 @@ describe('component: Type', () => { global: { directives: { cleanHtml: (identity) => identity }, - mocks: { $store: storeMock }, + mocks: { + $store: storeMock, $router: routerMock, $route: routeMock + }, stubs: { routerLink: createChildRenderingRouterLinkStub() }, }, }); @@ -211,7 +237,9 @@ describe('component: Type', () => { global: { directives: { cleanHtml: (identity) => identity }, - mocks: { $store: storeMock }, + mocks: { + $store: storeMock, $router: routerMock, $route: routeMock + }, stubs: { routerLink: createChildRenderingRouterLinkStub() }, }, }); @@ -230,7 +258,9 @@ describe('component: Type', () => { global: { directives: { cleanHtml: (identity) => identity }, - mocks: { $store: storeMock }, + mocks: { + $store: storeMock, $router: routerMock, $route: routeMock + }, stubs: { routerLink: createChildRenderingRouterLinkStub(), @@ -256,7 +286,9 @@ describe('component: Type', () => { global: { directives: { cleanHtml: (identity) => identity }, - mocks: { $store: storeMock }, + mocks: { + $store: storeMock, $router: routerMock, $route: routeMock + }, stubs: { routerLink: createChildRenderingRouterLinkStub(), @@ -277,7 +309,9 @@ describe('component: Type', () => { global: { directives: { cleanHtml: (identity) => identity }, - mocks: { $store: storeMock }, + mocks: { + $store: storeMock, $router: routerMock, $route: routeMock + }, stubs: { routerLink: createChildRenderingRouterLinkStub(), @@ -306,7 +340,9 @@ describe('component: Type', () => { global: { directives: { cleanHtml: (identity) => identity }, - mocks: { $store: storeMock }, + mocks: { + $store: storeMock, $router: routerMock, $route: routeMock + }, stubs: { routerLink: createChildRenderingRouterLinkStub(), @@ -327,7 +363,9 @@ describe('component: Type', () => { global: { directives: { cleanHtml: (identity) => identity }, - mocks: { $store: storeMock }, + mocks: { + $store: storeMock, $router: routerMock, $route: routeMock + }, stubs: { routerLink: createChildRenderingRouterLinkStub(), @@ -355,7 +393,9 @@ describe('component: Type', () => { currentStore: () => 'cluster', 'cluster/count': () => null, } - } + }, + $router: routerMock, + $route: routeMock }, stubs: { @@ -379,7 +419,9 @@ describe('component: Type', () => { global: { directives: { cleanHtml: (identity) => identity }, - mocks: { $store: storeMock }, + mocks: { + $store: storeMock, $router: routerMock, $route: routeMock + }, stubs: { routerLink: createChildRenderingRouterLinkStub(), @@ -400,7 +442,9 @@ describe('component: Type', () => { global: { directives: { cleanHtml: (identity) => identity }, - mocks: { $store: storeMock }, + mocks: { + $store: storeMock, $router: routerMock, $route: routeMock + }, stubs: { routerLink: createChildRenderingRouterLinkStub(),