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:34 UTC

(texera) branch gh-readonly-queue/main/pr-5234-968144bdfa9eb96df1024c3c4f5ba34afdcf8122 created (now d05a3b10ec)

This is an automated email from the ASF dual-hosted git repository.

github-merge-queue[bot] pushed a change to branch gh-readonly-queue/main/pr-5234-968144bdfa9eb96df1024c3c4f5ba34afdcf8122
in repository https://gitbox.apache.org/repos/asf/texera.git


      at d05a3b10ec test: unit tests for LandingPageComponent (#5234)

This branch includes the following new commits:

     new d05a3b10ec test: unit tests for LandingPageComponent (#5234)

The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.



(texera) 01/01: test: unit tests for LandingPageComponent (#5234)

Posted by gi...@apache.org.
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]);
+  });
+});