This is an automated email from the ASF dual-hosted git repository. github-merge-queue[bot] pushed a commit to branch gh-readonly-queue/main/pr-5232-0bfe9055e4285eb336abf7d62c08275fac79d4b7 in repository https://gitbox.apache.org/repos/asf/texera.git
commit aa3a1179864d5271773917a0baf36df95ed33ef4 Author: Matthew B. <[email protected]> AuthorDate: Tue Jun 2 16:38:15 2026 -0700 test: cover hub.component sidebar gating and routerLinks (#5232) ### What changes were proposed in this PR? - Replace the hub.component.spec.ts smoke test with nine tests covering default-input render, GuiConfigService injection, per-flag `sidebarTabs.*_enabled` gating, all-enabled rendering, exclusion of disabled tabs, `routerLink` bindings to the three routing constants, and `isLogin` input passthrough. - Wrap `HubComponent` in a test host `<ul nz-menu>` so `nz-menu-item` directives resolve their DI tokens the same way they do under the dashboard at runtime. - Read `routerLink` via the directive's `routerLinkInput` signal since the `routerLink` input is a write-only setter and `ng-reflect-router-link` is not populated in the Vitest environment. ### Any related issues, documentation, or discussions? Closes: #5224 ### How was this PR tested? - `yarn test --include='src/app/hub/component/hub.component.spec.ts'`: 9 passed, 0 failed. - `yarn format:fix`: 506 files unchanged. ### Was this PR authored or co-authored using generative AI tooling? Co-authored with Claude Opus 4.7 in compliance with ASF --------- Co-authored-by: Meng Wang <[email protected]> Co-authored-by: Claude Opus 4.8 (1M context) <[email protected]> --- .../src/app/hub/component/hub.component.spec.ts | 165 +++++++++++++++++++++ 1 file changed, 165 insertions(+) diff --git a/frontend/src/app/hub/component/hub.component.spec.ts b/frontend/src/app/hub/component/hub.component.spec.ts new file mode 100644 index 0000000000..5a88360639 --- /dev/null +++ b/frontend/src/app/hub/component/hub.component.spec.ts @@ -0,0 +1,165 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { Component } from "@angular/core"; +import { ComponentFixture, TestBed } from "@angular/core/testing"; +import { By } from "@angular/platform-browser"; +import { HttpClientTestingModule } from "@angular/common/http/testing"; +import { NoopAnimationsModule } from "@angular/platform-browser/animations"; +import { RouterTestingModule } from "@angular/router/testing"; +import { RouterLink } from "@angular/router"; +import { NzMenuModule } from "ng-zorro-antd/menu"; + +import { HubComponent } from "./hub.component"; +import { commonTestProviders } from "../../common/testing/test-utils"; +import { GuiConfigService } from "../../common/service/gui-config.service"; +import { SidebarTabs } from "../../common/type/gui-config"; +import { HOME, HUB_DATASET_RESULT, HUB_WORKFLOW_RESULT } from "../../app-routing.constant"; + +// Full SidebarTabs with all flags off; tests enable only the ones they need. +function makeSidebarTabs(overrides: Partial<SidebarTabs> = {}): SidebarTabs { + return { + hub_enabled: false, + home_enabled: false, + workflow_enabled: false, + dataset_enabled: false, + your_work_enabled: false, + projects_enabled: false, + workflows_enabled: false, + compute_enabled: false, + datasets_enabled: false, + quota_enabled: false, + forum_enabled: false, + about_enabled: false, + ...overrides, + }; +} + +// Host wraps HubComponent in `<ul nz-menu>` so nz-menu-item directives resolve their DI tokens. +@Component({ + template: `<ul nz-menu> + <texera-hub + [isLogin]="isLogin" + [sidebarTabs]="sidebarTabs"></texera-hub> + </ul>`, + imports: [HubComponent, NzMenuModule], +}) +class TestHostComponent { + isLogin = false; + sidebarTabs: SidebarTabs = makeSidebarTabs(); +} + +describe("HubComponent", () => { + let hostFixture: ComponentFixture<TestHostComponent>; + let host: TestHostComponent; + + function setup(isLogin: boolean, sidebarTabs: SidebarTabs): HubComponent { + TestBed.configureTestingModule({ + imports: [TestHostComponent, HttpClientTestingModule, NoopAnimationsModule, RouterTestingModule.withRoutes([])], + providers: [...commonTestProviders], + }); + hostFixture = TestBed.createComponent(TestHostComponent); + host = hostFixture.componentInstance; + host.isLogin = isLogin; + host.sidebarTabs = sidebarTabs; + hostFixture.detectChanges(); + return hostFixture.debugElement.query(By.directive(HubComponent)).componentInstance as HubComponent; + } + + // Text of every rendered menu item. + function renderedMenuLabels(): string[] { + return hostFixture.debugElement + .queryAll(By.css("[nz-menu-item]")) + .map(de => (de.nativeElement.textContent ?? "").trim()); + } + + // RouterLink target of the menu item containing `label`. + function routerLinkFor(label: string): string { + const item = hostFixture.debugElement + .queryAll(By.css("[nz-menu-item]")) + .find(de => (de.nativeElement.textContent ?? "").includes(label)); + expect(item).toBeTruthy(); + // `routerLink` is a write-only setter; read the resolved value off the routerLinkInput signal. + const link = item!.injector.get(RouterLink) as unknown as { routerLinkInput: () => string | string[] }; + return ([] as string[]).concat(link.routerLinkInput()).join(""); + } + + it("creates with default isLogin = false and an empty sidebarTabs (no menu items render)", () => { + const component = setup(false, makeSidebarTabs()); + expect(component).toBeTruthy(); + expect(component.isLogin).toBe(false); + expect(renderedMenuLabels().length).toBe(0); + }); + + it("passes the isLogin input through to the component", () => { + const component = setup(true, makeSidebarTabs()); + expect(component.isLogin).toBe(true); + }); + + it("injects GuiConfigService and exposes it for template gating", () => { + const component = setup(false, makeSidebarTabs()); + expect((component as unknown as { config: GuiConfigService }).config).toBe(TestBed.inject(GuiConfigService)); + }); + + it("renders only the Home item when home_enabled is the only flag set", () => { + setup(false, makeSidebarTabs({ home_enabled: true })); + const labels = renderedMenuLabels(); + expect(labels.length).toBe(1); + expect(labels[0]).toContain("Home"); + }); + + it("renders only the Workflows item when workflow_enabled is the only flag set", () => { + setup(false, makeSidebarTabs({ workflow_enabled: true })); + const labels = renderedMenuLabels(); + expect(labels.length).toBe(1); + expect(labels[0]).toContain("Workflows"); + }); + + it("renders only the Datasets item when dataset_enabled is the only flag set", () => { + setup(false, makeSidebarTabs({ dataset_enabled: true })); + const labels = renderedMenuLabels(); + expect(labels.length).toBe(1); + expect(labels[0]).toContain("Datasets"); + }); + + it("renders all three menu items when home, workflow, and dataset flags are enabled", () => { + setup(false, makeSidebarTabs({ home_enabled: true, workflow_enabled: true, dataset_enabled: true })); + const labels = renderedMenuLabels(); + expect(labels.length).toBe(3); + expect(labels.some(l => l.includes("Home"))).toBe(true); + expect(labels.some(l => l.includes("Workflows"))).toBe(true); + expect(labels.some(l => l.includes("Datasets"))).toBe(true); + }); + + it("excludes disabled tabs while rendering enabled ones", () => { + setup(false, makeSidebarTabs({ home_enabled: true, workflow_enabled: false, dataset_enabled: true })); + const labels = renderedMenuLabels(); + expect(labels.length).toBe(2); + expect(labels.some(l => l.includes("Home"))).toBe(true); + expect(labels.some(l => l.includes("Datasets"))).toBe(true); + expect(labels.some(l => l.includes("Workflows"))).toBe(false); + }); + + it("binds each menu item's routerLink to the correct routing constant", () => { + setup(false, makeSidebarTabs({ home_enabled: true, workflow_enabled: true, dataset_enabled: true })); + expect(routerLinkFor("Home")).toBe(HOME); + expect(routerLinkFor("Workflows")).toBe(HUB_WORKFLOW_RESULT); + expect(routerLinkFor("Datasets")).toBe(HUB_DATASET_RESULT); + }); +});
