You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@texera.apache.org by gi...@apache.org on 2026/05/27 05:11:35 UTC
(texera) 01/01: test: unit tests for LandingPageComponent (#5234)
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-5234-968144bdfa9eb96df1024c3c4f5ba34afdcf8122
in repository https://gitbox.apache.org/repos/asf/texera.git
commit d05a3b10ec16aca525518b0a1d590992cb4f789c
Author: Matthew B. <mg...@uci.edu>
AuthorDate: Tue May 26 22:11:12 2026 -0700
test: unit tests for LandingPageComponent (#5234)
### What changes were proposed in this PR?
- Add `landing-page.component.spec.ts` covering creation, the
constructor's `userChanged()` subscription (login/logout transitions
update `isLogin` and `currentUid`), `ngOnInit` calling both
`getWorkflowCount` and `loadTops`, count population from
`HubService.getCount`, the three top-loved buckets populated by
`loadTops`, the `loadTops` error path logging via `console.error`,
`getTopLovedEntries` enriching items via
`SearchService.extendSearchResultsWithHubActivityInfo(items, true,
["access"])` per action and returning a map keyed by action, and
`navigateToSearch` routing for `"workflow"`, `"dataset"`, and unknown
types.
- Stub `HubService`, `SearchService`, `WorkflowPersistService`, and
`DatasetService`; reuse `StubUserService` for a real `userChanged()`
subject; replace `Router.navigate` with a vitest spy.
### Any related issues, documentation, or discussions?
Closes: #5225
### How was this PR tested?
- Ran `yarn ng test --watch=false
--include='src/app/hub/component/landing-page/landing-page.component.spec.ts'`,
10/10 tests pass.
- Ran `yarn format:fix` (1 file formatted, 505 unchanged).
### Was this PR authored or co-authored using generative AI tooling?
Co-authored with Claude Opus 4.7 in compliance with ASF
---
.../landing-page/landing-page.component.spec.ts | 213 +++++++++++++++++++++
1 file changed, 213 insertions(+)
diff --git a/frontend/src/app/hub/component/landing-page/landing-page.component.spec.ts b/frontend/src/app/hub/component/landing-page/landing-page.component.spec.ts
new file mode 100644
index 0000000000..8585ee1c54
--- /dev/null
+++ b/frontend/src/app/hub/component/landing-page/landing-page.component.spec.ts
@@ -0,0 +1,213 @@
+/**
+ * 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 { ComponentFixture, TestBed } from "@angular/core/testing";
+import { Router } from "@angular/router";
+import { RouterTestingModule } from "@angular/router/testing";
+import { of, throwError } from "rxjs";
+import { vi } from "vitest";
+
+import { LandingPageComponent } from "./landing-page.component";
+import { ActionType, EntityType, HubService } from "../../service/hub.service";
+import { SearchService } from "../../../dashboard/service/user/search.service";
+import { UserService } from "../../../common/service/user/user.service";
+import { StubUserService } from "../../../common/service/user/stub-user.service";
+import { WorkflowPersistService } from "../../../common/service/workflow-persist/workflow-persist.service";
+import { DatasetService } from "../../../dashboard/service/user/dataset/dataset.service";
+import {
+ DASHBOARD_HOME,
+ DASHBOARD_HUB_DATASET_RESULT,
+ DASHBOARD_HUB_WORKFLOW_RESULT,
+} from "../../../app-routing.constant";
+import { commonTestProviders } from "../../../common/testing/test-utils";
+
+describe("LandingPageComponent", () => {
+ let component: LandingPageComponent;
+ let fixture: ComponentFixture<LandingPageComponent>;
+ let hubServiceStub: {
+ getCount: ReturnType<typeof vi.fn>;
+ getTops: ReturnType<typeof vi.fn>;
+ };
+ let searchServiceStub: {
+ extendSearchResultsWithHubActivityInfo: ReturnType<typeof vi.fn>;
+ };
+ let userService: StubUserService;
+ let routerNavigateSpy: ReturnType<typeof vi.fn>;
+
+ // Workflow tops are returned for both Like and Clone; dataset tops for Like only.
+ // Each call to `extendSearchResultsWithHubActivityInfo` is given a tag so the
+ // tests can assert which action bucket each enriched payload landed in.
+ const workflowLikeItems = [{ id: "wf-like-item" }] as any;
+ const workflowCloneItems = [{ id: "wf-clone-item" }] as any;
+ const datasetLikeItems = [{ id: "ds-like-item" }] as any;
+ const workflowLikeEnriched = [{ id: "wf-like-enriched" }] as any;
+ const workflowCloneEnriched = [{ id: "wf-clone-enriched" }] as any;
+ const datasetLikeEnriched = [{ id: "ds-like-enriched" }] as any;
+
+ function configureModule() {
+ hubServiceStub = {
+ getCount: vi.fn((entityType: EntityType) => {
+ if (entityType === EntityType.Workflow) return of(42);
+ if (entityType === EntityType.Dataset) return of(7);
+ return of(0);
+ }),
+ getTops: vi.fn((entityType: EntityType, _actions: ActionType[], _uid?: number) => {
+ if (entityType === EntityType.Workflow) {
+ return of({ [ActionType.Like]: workflowLikeItems, [ActionType.Clone]: workflowCloneItems });
+ }
+ return of({ [ActionType.Like]: datasetLikeItems });
+ }),
+ };
+
+ searchServiceStub = {
+ extendSearchResultsWithHubActivityInfo: vi.fn((items: any[]) => {
+ if (items === workflowLikeItems) return of(workflowLikeEnriched);
+ if (items === workflowCloneItems) return of(workflowCloneEnriched);
+ if (items === datasetLikeItems) return of(datasetLikeEnriched);
+ return of([]);
+ }),
+ };
+
+ TestBed.configureTestingModule({
+ imports: [LandingPageComponent, RouterTestingModule.withRoutes([])],
+ providers: [
+ { provide: HubService, useValue: hubServiceStub },
+ { provide: SearchService, useValue: searchServiceStub },
+ { provide: UserService, useClass: StubUserService },
+ { provide: WorkflowPersistService, useValue: {} },
+ { provide: DatasetService, useValue: {} },
+ ...commonTestProviders,
+ ],
+ });
+
+ userService = TestBed.inject(UserService) as unknown as StubUserService;
+ const router = TestBed.inject(Router);
+ routerNavigateSpy = vi.fn().mockResolvedValue(true);
+ router.navigate = routerNavigateSpy as any;
+ }
+
+ function build() {
+ fixture = TestBed.createComponent(LandingPageComponent);
+ component = fixture.componentInstance;
+ }
+
+ beforeEach(() => {
+ configureModule();
+ });
+
+ it("should create", () => {
+ build();
+ expect(component).toBeTruthy();
+ });
+
+ it("updates isLogin and currentUid when userChanged() emits", () => {
+ build();
+ // Emit a logged-out state.
+ userService.user = undefined;
+ userService.userChangeSubject.next(undefined);
+ expect(component.isLogin).toBe(false);
+ expect(component.currentUid).toBeUndefined();
+
+ // Emit a logged-in state.
+ const newUser = { uid: 99, name: "x", email: "x@x", role: "REGULAR" } as any;
+ userService.user = newUser;
+ userService.userChangeSubject.next(newUser);
+ expect(component.isLogin).toBe(true);
+ expect(component.currentUid).toBe(99);
+ });
+
+ it("ngOnInit invokes getWorkflowCount and loadTops", () => {
+ build();
+ const countSpy = vi.spyOn(component, "getWorkflowCount");
+ const loadSpy = vi.spyOn(component, "loadTops").mockResolvedValue(undefined as any);
+ component.ngOnInit();
+ expect(countSpy).toHaveBeenCalledTimes(1);
+ expect(loadSpy).toHaveBeenCalledTimes(1);
+ });
+
+ it("getWorkflowCount populates workflowCount and datasetCount from HubService.getCount", () => {
+ build();
+ component.getWorkflowCount();
+ expect(hubServiceStub.getCount).toHaveBeenCalledWith(EntityType.Workflow);
+ expect(hubServiceStub.getCount).toHaveBeenCalledWith(EntityType.Dataset);
+ expect(component.workflowCount).toBe(42);
+ expect(component.datasetCount).toBe(7);
+ });
+
+ it("loadTops resolves workflow Like/Clone and dataset Like buckets", async () => {
+ build();
+ await component.loadTops();
+
+ expect(hubServiceStub.getTops).toHaveBeenCalledWith(
+ EntityType.Workflow,
+ [ActionType.Like, ActionType.Clone],
+ component.currentUid
+ );
+ expect(hubServiceStub.getTops).toHaveBeenCalledWith(EntityType.Dataset, [ActionType.Like], component.currentUid);
+
+ expect(component.topLovedWorkflows).toBe(workflowLikeEnriched);
+ expect(component.topClonedWorkflows).toBe(workflowCloneEnriched);
+ expect(component.topLovedDatasets).toBe(datasetLikeEnriched);
+ });
+
+ it("loadTops swallows errors and logs them via console.error", async () => {
+ hubServiceStub.getTops.mockReturnValueOnce(throwError(() => new Error("boom")));
+ const errorSpy = vi.spyOn(console, "error").mockImplementation(() => {});
+ build();
+ await component.loadTops();
+ expect(errorSpy).toHaveBeenCalledWith("Failed to load top entries:", expect.any(Error));
+ // Arrays remain at their initial empty state.
+ expect(component.topLovedWorkflows).toEqual([]);
+ expect(component.topClonedWorkflows).toEqual([]);
+ expect(component.topLovedDatasets).toEqual([]);
+ errorSpy.mockRestore();
+ });
+
+ it("getTopLovedEntries extends each action's items with SearchService and returns a map keyed by action", async () => {
+ build();
+ const result = await component.getTopLovedEntries(EntityType.Workflow, [ActionType.Like, ActionType.Clone]);
+
+ expect(searchServiceStub.extendSearchResultsWithHubActivityInfo).toHaveBeenCalledWith(workflowLikeItems, true, [
+ "access",
+ ]);
+ expect(searchServiceStub.extendSearchResultsWithHubActivityInfo).toHaveBeenCalledWith(workflowCloneItems, true, [
+ "access",
+ ]);
+ expect(result[ActionType.Like]).toBe(workflowLikeEnriched);
+ expect(result[ActionType.Clone]).toBe(workflowCloneEnriched);
+ });
+
+ it("navigateToSearch routes to the workflow hub result for 'workflow'", () => {
+ build();
+ component.navigateToSearch("workflow");
+ expect(routerNavigateSpy).toHaveBeenCalledWith([DASHBOARD_HUB_WORKFLOW_RESULT]);
+ });
+
+ it("navigateToSearch routes to the dataset hub result for 'dataset'", () => {
+ build();
+ component.navigateToSearch("dataset");
+ expect(routerNavigateSpy).toHaveBeenCalledWith([DASHBOARD_HUB_DATASET_RESULT]);
+ });
+
+ it("navigateToSearch routes to the dashboard home for an unknown type", () => {
+ build();
+ component.navigateToSearch("something-else");
+ expect(routerNavigateSpy).toHaveBeenCalledWith([DASHBOARD_HOME]);
+ });
+});