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-d8c254c75a024f00106528dfb66f4ccc4084f012
in repository https://gitbox.apache.org/repos/asf/texera.git

commit a4a3df5af33ef4b67ca5ebb3d4be52efd00d4254
Author: Matthew B. <[email protected]>
AuthorDate: Wed May 27 23:47:49 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]);
+  });
+});

Reply via email to