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

commit f967080e387f78f0fcf71025ccca1e20ba33ad57
Author: Matthew B. <[email protected]>
AuthorDate: Tue May 26 23:59:14 2026 -0700

    test: add spec for UserDatasetListItemComponent (#5239)
    
    ### What changes were proposed in this PR?
    - Add `user-dataset-list-item.component.spec.ts` covering the previously
    untested `UserDatasetListItemComponent`.
    - Tests use a `TestHostComponent` wrapper inside `<nz-list>` (the
    component is rooted at `<nz-list-item>` and otherwise fails DI),
    mirroring the sibling `UserWorkflowListItemComponent` and
    `UserProjectListItemComponent` specs.
    - Coverage: `entry` getter (throws when unset, returns when set),
    `dataset` getter (returns `entry.dataset`, throws when missing),
    `confirmUpdateDatasetCustomName` and
    `confirmUpdateDatasetCustomDescription` (no-op when unchanged, success
    path updates the field and resets the editing flag, error path raises a
    `NotificationService.error` and still resets the flag),
    `onClickOpenShareAccess` (opens `ShareAccessComponent` with the expected
    `nzData` payload, footer, title, centered options, and toggles
    `writeAccess` based on `accessPrivilege`), the default value of the
    `editable` input, and the `deleted` / `duplicated` / `refresh` outputs
    as `EventEmitter`s.
    ### Any related issues, documentation, or discussions?
    Closes: #5230
    ### How was this PR tested?
    - Ran `yarn ng test --watch=false
    
--include="src/app/dashboard/component/user/user-dataset/user-dataset-list-item/user-dataset-list-item.component.spec.ts"`:
    15 tests pass.
    ### Was this PR authored or co-authored using generative AI tooling?
    Co-authored with Claude Opus 4.7 in compliance with ASF
---
 .../user-dataset-list-item.component.spec.ts       | 281 +++++++++++++++++++++
 1 file changed, 281 insertions(+)

diff --git 
a/frontend/src/app/dashboard/component/user/user-dataset/user-dataset-list-item/user-dataset-list-item.component.spec.ts
 
b/frontend/src/app/dashboard/component/user/user-dataset/user-dataset-list-item/user-dataset-list-item.component.spec.ts
new file mode 100644
index 0000000000..eba371fdb4
--- /dev/null
+++ 
b/frontend/src/app/dashboard/component/user/user-dataset/user-dataset-list-item/user-dataset-list-item.component.spec.ts
@@ -0,0 +1,281 @@
+/**
+ * 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, EventEmitter, ViewChild } from "@angular/core";
+import { ComponentFixture, TestBed } from "@angular/core/testing";
+import { HttpClientTestingModule } from "@angular/common/http/testing";
+import { provideRouter } from "@angular/router";
+import { NzListComponent } from "ng-zorro-antd/list";
+import { NzModalService } from "ng-zorro-antd/modal";
+import { of, throwError } from "rxjs";
+import type { Mocked } from "vitest";
+import { UserDatasetListItemComponent } from 
"./user-dataset-list-item.component";
+import { DatasetService } from 
"../../../../service/user/dataset/dataset.service";
+import { NotificationService } from 
"../../../../../common/service/notification/notification.service";
+import { ShareAccessComponent } from 
"../../share-access/share-access.component";
+import { DashboardDataset } from 
"../../../../type/dashboard-dataset.interface";
+import { commonTestProviders } from "../../../../../common/testing/test-utils";
+
+// UserDatasetListItemComponent is rooted at <nz-list-item>; instantiating it
+// outside an <nz-list> host throws "No provider found for NzListComponent".
+@Component({
+  standalone: true,
+  imports: [NzListComponent, UserDatasetListItemComponent],
+  template: `
+    <nz-list>
+      <texera-user-dataset-list-item
+        [entry]="entry"
+        [editable]="editable"></texera-user-dataset-list-item>
+    </nz-list>
+  `,
+})
+class TestHostComponent {
+  entry!: DashboardDataset;
+  editable = true;
+  @ViewChild(UserDatasetListItemComponent, { static: true }) inner!: 
UserDatasetListItemComponent;
+}
+
+function makeEntry(overrides: Partial<DashboardDataset> = {}): 
DashboardDataset {
+  return {
+    isOwner: true,
+    ownerEmail: "[email protected]",
+    accessPrivilege: "WRITE",
+    size: 0,
+    dataset: {
+      did: 1,
+      ownerUid: 1,
+      name: "dataset-1",
+      isPublic: false,
+      isDownloadable: true,
+      storagePath: undefined,
+      description: "original description",
+      creationTime: 0,
+      coverImage: undefined,
+    },
+    ...overrides,
+  };
+}
+
+describe("UserDatasetListItemComponent", () => {
+  let component: UserDatasetListItemComponent;
+  let fixture: ComponentFixture<TestHostComponent>;
+  let datasetService: Mocked<DatasetService>;
+  let notificationService: Mocked<NotificationService>;
+  let modalService: NzModalService;
+
+  beforeEach(async () => {
+    const datasetServiceSpy = {
+      updateDatasetName: vi.fn(),
+      updateDatasetDescription: vi.fn(),
+    };
+    const notificationServiceSpy = {
+      error: vi.fn(),
+      success: vi.fn(),
+      info: vi.fn(),
+      warning: vi.fn(),
+      loading: vi.fn(),
+      blank: vi.fn(),
+    };
+
+    await TestBed.configureTestingModule({
+      imports: [TestHostComponent, HttpClientTestingModule],
+      providers: [
+        { provide: DatasetService, useValue: datasetServiceSpy },
+        { provide: NotificationService, useValue: notificationServiceSpy },
+        NzModalService,
+        provideRouter([]),
+        ...commonTestProviders,
+      ],
+    }).compileComponents();
+
+    datasetService = TestBed.inject(DatasetService) as unknown as 
Mocked<DatasetService>;
+    notificationService = TestBed.inject(NotificationService) as unknown as 
Mocked<NotificationService>;
+    modalService = TestBed.inject(NzModalService);
+  });
+
+  beforeEach(() => {
+    fixture = TestBed.createComponent(TestHostComponent);
+    fixture.componentInstance.entry = makeEntry();
+    fixture.componentInstance.editable = true;
+    fixture.detectChanges();
+    component = fixture.componentInstance.inner;
+  });
+
+  it("should create", () => {
+    expect(component).toBeTruthy();
+  });
+
+  describe("entry getter", () => {
+    it("throws when accessed before being set", () => {
+      // Build a bare component without going through the host (the host always
+      // assigns entry on first change detection).
+      const bare = new UserDatasetListItemComponent(
+        {} as NzModalService,
+        {} as DatasetService,
+        {} as NotificationService
+      );
+      expect(() => bare.entry).toThrowError("entry property must be provided 
to UserDatasetListItemComponent.");
+    });
+
+    it("returns the value once set", () => {
+      const entry = makeEntry({ accessPrivilege: "READ" });
+      const bare = new UserDatasetListItemComponent(
+        {} as NzModalService,
+        {} as DatasetService,
+        {} as NotificationService
+      );
+      bare.entry = entry;
+      expect(bare.entry).toBe(entry);
+    });
+  });
+
+  describe("dataset getter", () => {
+    it("returns entry.dataset when present", () => {
+      expect(component.dataset).toBe(component.entry.dataset);
+    });
+
+    it("throws when entry.dataset is missing", () => {
+      const bare = new UserDatasetListItemComponent(
+        {} as NzModalService,
+        {} as DatasetService,
+        {} as NotificationService
+      );
+      bare.entry = { ...makeEntry(), dataset: undefined as unknown as 
DashboardDataset["dataset"] };
+      expect(() => bare.dataset).toThrowError(
+        "Incorrect type of DashboardEntry provided to 
UserDatasetListItemComponent. Entry must be dataset."
+      );
+    });
+  });
+
+  describe("confirmUpdateDatasetCustomName", () => {
+    it("is a no-op when the name has not changed", () => {
+      const current = component.entry.dataset.name;
+      component.confirmUpdateDatasetCustomName(current);
+      expect(datasetService.updateDatasetName).not.toHaveBeenCalled();
+    });
+
+    it("updates the dataset name on success and clears editingName", () => {
+      const newName = "renamed-dataset";
+      component.editingName = true;
+      datasetService.updateDatasetName.mockReturnValue(of({} as Response));
+
+      component.confirmUpdateDatasetCustomName(newName);
+
+      
expect(datasetService.updateDatasetName).toHaveBeenCalledExactlyOnceWith(1, 
newName);
+      expect(component.entry.dataset.name).toBe(newName);
+      expect(component.editingName).toBe(false);
+    });
+
+    it("notifies the user on error and still clears editingName", () => {
+      const originalName = component.entry.dataset.name;
+      component.editingName = true;
+      datasetService.updateDatasetName.mockReturnValue(throwError(() => new 
Error("boom")));
+
+      component.confirmUpdateDatasetCustomName("renamed-dataset");
+
+      
expect(notificationService.error).toHaveBeenCalledExactlyOnceWith("Update 
dataset name failed");
+      expect(component.entry.dataset.name).toBe(originalName);
+      expect(component.editingName).toBe(false);
+    });
+  });
+
+  describe("confirmUpdateDatasetCustomDescription", () => {
+    it("is a no-op when the description has not changed", () => {
+      const current = component.entry.dataset.description;
+      component.confirmUpdateDatasetCustomDescription(current);
+      expect(datasetService.updateDatasetDescription).not.toHaveBeenCalled();
+    });
+
+    it("updates the dataset description on success and clears 
editingDescription", () => {
+      const newDescription = "updated description";
+      component.editingDescription = true;
+      datasetService.updateDatasetDescription.mockReturnValue(of({} as 
Response));
+
+      component.confirmUpdateDatasetCustomDescription(newDescription);
+
+      
expect(datasetService.updateDatasetDescription).toHaveBeenCalledExactlyOnceWith(1,
 newDescription);
+      expect(component.entry.dataset.description).toBe(newDescription);
+      expect(component.editingDescription).toBe(false);
+    });
+
+    it("notifies the user on error and still clears editingDescription", () => 
{
+      const originalDescription = component.entry.dataset.description;
+      component.editingDescription = true;
+      datasetService.updateDatasetDescription.mockReturnValue(throwError(() => 
new Error("boom")));
+
+      component.confirmUpdateDatasetCustomDescription("updated description");
+
+      
expect(notificationService.error).toHaveBeenCalledExactlyOnceWith("Update 
dataset description failed");
+      expect(component.entry.dataset.description).toBe(originalDescription);
+      expect(component.editingDescription).toBe(false);
+    });
+  });
+
+  describe("onClickOpenShareAccess", () => {
+    it("opens the share-access modal with the expected nzData and options", () 
=> {
+      const createSpy = vi.spyOn(modalService, "create").mockReturnValue({} as 
any);
+
+      component.onClickOpenShareAccess();
+
+      expect(createSpy).toHaveBeenCalledExactlyOnceWith({
+        nzContent: ShareAccessComponent,
+        nzData: {
+          writeAccess: true,
+          type: "dataset",
+          id: 1,
+        },
+        nzFooter: null,
+        nzTitle: "Share this dataset with others",
+        nzCentered: true,
+      });
+    });
+
+    it("sets writeAccess to false when accessPrivilege is not WRITE", () => {
+      fixture.componentInstance.entry = makeEntry({ accessPrivilege: "READ" });
+      fixture.detectChanges();
+      const inner = fixture.componentInstance.inner;
+      const createSpy = vi.spyOn(modalService, "create").mockReturnValue({} as 
any);
+
+      inner.onClickOpenShareAccess();
+
+      expect(createSpy).toHaveBeenCalledExactlyOnceWith(
+        expect.objectContaining({
+          nzData: expect.objectContaining({ writeAccess: false }),
+        })
+      );
+    });
+  });
+
+  describe("inputs and outputs", () => {
+    it("defaults editable to false", () => {
+      const bare = new UserDatasetListItemComponent(
+        {} as NzModalService,
+        {} as DatasetService,
+        {} as NotificationService
+      );
+      expect(bare.editable).toBe(false);
+    });
+
+    it("exposes deleted, duplicated, and refresh as EventEmitters", () => {
+      expect(component.deleted).toBeInstanceOf(EventEmitter);
+      expect(component.duplicated).toBeInstanceOf(EventEmitter);
+      expect(component.refresh).toBeInstanceOf(EventEmitter);
+    });
+  });
+});

Reply via email to