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/07/20 06:52:09 UTC
(texera) 01/03: test(frontend): add unit tests for PublicProjectComponent (#6575)
This is an automated email from the ASF dual-hosted git repository.
github-merge-queue[bot] pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/texera.git
commit da3b2ffb2988e001b39066f4b8fef76f91539972
Author: Xinyuan Lin <xi...@uci.edu>
AuthorDate: Sun Jul 19 23:29:42 2026 -0700
test(frontend): add unit tests for PublicProjectComponent (#6575)
### What changes were proposed in this PR?
Adds a Vitest unit spec for `PublicProjectComponent`, which previously
had no test coverage. The 10 tests cover:
- `ngOnInit` loading `publicProjectEntries` and rendering table rows.
- reading `disabledList` from `NZ_MODAL_DATA`.
- `updateCheckedSet` add/remove and `onItemChecked`.
- `onAllChecked(true/false)` checking/unchecking every entry.
- `refreshCheckedStatus` for all-, none-, and some-selected
(`checked`/`indeterminate`).
- `addPublicProjects` calling the service with `Array.from(checkedList)`
and destroying the modal on success.
The component injects `NzModalRef` and `NZ_MODAL_DATA`, both provided in
the TestBed.
### Any related issues, documentation, discussions?
Closes #6572.
### How was this PR tested?
`npx ng test --watch=false
--include='**/public-project.component.spec.ts'` -> 10/10 passing. `yarn
format:ci` passes.
### Was this PR authored or co-authored using generative AI tooling?
Generated-by: Claude Code (Opus 4.8 [1M context])
---------
Signed-off-by: Xinyuan Lin <xi...@uci.edu>
Co-authored-by: Copilot Autofix powered by AI <17...@users.noreply.github.com>
---
.../public-project.component.spec.ts | 187 +++++++++++++++++++++
1 file changed, 187 insertions(+)
diff --git a/frontend/src/app/dashboard/component/user/user-project/public-project/public-project.component.spec.ts b/frontend/src/app/dashboard/component/user/user-project/public-project/public-project.component.spec.ts
new file mode 100644
index 0000000000..452e22ab01
--- /dev/null
+++ b/frontend/src/app/dashboard/component/user/user-project/public-project/public-project.component.spec.ts
@@ -0,0 +1,187 @@
+/**
+ * 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 { NoopAnimationsModule } from "@angular/platform-browser/animations";
+import { of } from "rxjs";
+import { NZ_MODAL_DATA, NzModalRef } from "ng-zorro-antd/modal";
+import { PublicProjectComponent } from "./public-project.component";
+import { PublicProjectService } from "../../../../service/user/public-project/public-project.service";
+import { PublicProject } from "../../../../type/dashboard-project.interface";
+import { commonTestProviders } from "../../../../../common/testing/test-utils";
+
+describe("PublicProjectComponent", () => {
+ let fixture: ComponentFixture<PublicProjectComponent>;
+ let component: PublicProjectComponent;
+
+ const sampleProjects: PublicProject[] = [
+ { pid: 1, name: "Alpha", owner: "alice", creationTime: 1_000 },
+ { pid: 2, name: "Bravo", owner: "bob", creationTime: 2_000 },
+ { pid: 3, name: "Charlie", owner: "carol", creationTime: 3_000 },
+ ];
+
+ let mockPublicProjectService: {
+ getPublicProjects: ReturnType<typeof vi.fn>;
+ addPublicProjects: ReturnType<typeof vi.fn>;
+ };
+ let modalRef: { destroy: ReturnType<typeof vi.fn> };
+ let disabledList: Set<number>;
+
+ beforeEach(async () => {
+ mockPublicProjectService = {
+ getPublicProjects: vi.fn().mockReturnValue(of(sampleProjects)),
+ addPublicProjects: vi.fn().mockReturnValue(of(undefined)),
+ };
+ modalRef = { destroy: vi.fn() };
+ // pid 2 is disabled to verify the component reads disabledList from NZ_MODAL_DATA.
+ disabledList = new Set<number>([2]);
+
+ await TestBed.configureTestingModule({
+ imports: [PublicProjectComponent, NoopAnimationsModule],
+ providers: [
+ ...commonTestProviders,
+ { provide: PublicProjectService, useValue: mockPublicProjectService },
+ { provide: NzModalRef, useValue: modalRef },
+ { provide: NZ_MODAL_DATA, useValue: { disabledList } },
+ ],
+ }).compileComponents();
+
+ fixture = TestBed.createComponent(PublicProjectComponent);
+ component = fixture.componentInstance;
+ });
+
+ afterEach(() => {
+ fixture?.destroy();
+ document.querySelectorAll(".cdk-overlay-container").forEach(c => (c.innerHTML = ""));
+ });
+
+ it("should create and read disabledList from NZ_MODAL_DATA", () => {
+ fixture.detectChanges();
+ expect(component).toBeTruthy();
+ expect(component.disabledList).toBe(disabledList);
+ expect(component.disabledList.has(2)).toBe(true);
+ });
+
+ it("ngOnInit loads publicProjectEntries from getPublicProjects and renders the rows", () => {
+ fixture.detectChanges();
+ expect(mockPublicProjectService.getPublicProjects).toHaveBeenCalledTimes(1);
+ expect(component.publicProjectEntries).toEqual(sampleProjects);
+
+ const rows = (fixture.nativeElement as HTMLElement).querySelectorAll("tbody tr");
+ expect(rows.length).toBe(sampleProjects.length);
+ const text = (fixture.nativeElement as HTMLElement).textContent ?? "";
+ expect(text).toContain("Alpha");
+ expect(text).toContain("alice");
+ expect(text).toContain("Selected 0 items");
+ });
+
+ it("updateCheckedSet adds an id when checked and removes it when unchecked", () => {
+ fixture.detectChanges();
+
+ component.updateCheckedSet(1, true);
+ expect(component.checkedList.has(1)).toBe(true);
+
+ component.updateCheckedSet(1, false);
+ expect(component.checkedList.has(1)).toBe(false);
+ });
+
+ it("onItemChecked updates the set and refreshes the checked status", () => {
+ fixture.detectChanges();
+ const refreshSpy = vi.spyOn(component, "refreshCheckedStatus");
+
+ component.onItemChecked(1, true);
+
+ expect(component.checkedList.has(1)).toBe(true);
+ expect(refreshSpy).toHaveBeenCalledTimes(1);
+ // Only 1 of 3 selected => indeterminate, not fully checked.
+ expect(component.checked).toBe(false);
+ expect(component.indeterminate).toBe(true);
+
+ component.onItemChecked(1, false);
+ expect(component.checkedList.has(1)).toBe(false);
+ expect(refreshSpy).toHaveBeenCalledTimes(2);
+ expect(component.indeterminate).toBe(false);
+ });
+
+ it("onAllChecked(true) selects every entry", () => {
+ fixture.detectChanges();
+
+ component.onAllChecked(true);
+
+ sampleProjects.forEach(p => expect(component.checkedList.has(p.pid)).toBe(true));
+ expect(component.checkedList.size).toBe(sampleProjects.length);
+ expect(component.checked).toBe(true);
+ expect(component.indeterminate).toBe(false);
+ });
+
+ it("onAllChecked(false) unchecks all entries", () => {
+ fixture.detectChanges();
+ component.onAllChecked(true);
+
+ component.onAllChecked(false);
+
+ expect(component.checkedList.size).toBe(0);
+ expect(component.checked).toBe(false);
+ expect(component.indeterminate).toBe(false);
+ });
+
+ describe("refreshCheckedStatus", () => {
+ beforeEach(() => fixture.detectChanges());
+
+ it("all selected => checked true, indeterminate false", () => {
+ sampleProjects.forEach(p => component.checkedList.add(p.pid));
+
+ component.refreshCheckedStatus();
+
+ expect(component.checked).toBe(true);
+ expect(component.indeterminate).toBe(false);
+ });
+
+ it("none selected => checked false, indeterminate false", () => {
+ component.checkedList.clear();
+
+ component.refreshCheckedStatus();
+
+ expect(component.checked).toBe(false);
+ expect(component.indeterminate).toBe(false);
+ });
+
+ it("some selected => checked false, indeterminate true", () => {
+ component.checkedList.add(sampleProjects[0].pid);
+
+ component.refreshCheckedStatus();
+
+ expect(component.checked).toBe(false);
+ expect(component.indeterminate).toBe(true);
+ });
+ });
+
+ it("addPublicProjects calls the service with the checked ids and destroys the modal on success", () => {
+ fixture.detectChanges();
+ component.checkedList = new Set<number>([1, 3]);
+
+ component.addPublicProjects();
+
+ expect(mockPublicProjectService.addPublicProjects).toHaveBeenCalledTimes(1);
+ const [[checkedIds]] = mockPublicProjectService.addPublicProjects.mock.calls;
+ expect(checkedIds).toHaveLength(2);
+ expect(checkedIds).toEqual(expect.arrayContaining([1, 3]));
+ expect(modalRef.destroy).toHaveBeenCalledTimes(1);
+ });
+});