aglinxinyuan commented on code in PR #6358:
URL: https://github.com/apache/texera/pull/6358#discussion_r3565440700


##########
frontend/src/app/dashboard/component/user/user-dataset/user-dataset-explorer/dataset-detail.component.spec.ts:
##########
@@ -334,3 +338,653 @@ describe("DatasetDetailComponent upload queue", () => {
     expect(component.pendingChangesCount).toBe(1);
   });
 });
+
+describe("DatasetDetailComponent behavior", () => {
+  let fixture: ComponentFixture<DatasetDetailComponent>;
+  let component: DatasetDetailComponent;
+
+  type MockService = Record<string, ReturnType<typeof vi.fn>>;
+  let datasetServiceStub: MockService;
+  let notificationServiceStub: MockService;
+  let downloadServiceStub: MockService;
+  let hubServiceStub: MockService;
+  let adminSettingsServiceStub: MockService;
+
+  const CREATION_TS = 1_700_000_000_000;
+
+  const makeDataset = (overrides: Partial<Dataset> = {}): Dataset => ({
+    did: 5,
+    ownerUid: 9,
+    name: "ds",
+    isPublic: false,
+    isDownloadable: true,
+    storagePath: undefined,
+    description: "desc",
+    creationTime: undefined,
+    coverImage: undefined,
+    ...overrides,
+  });
+
+  const makeDashboardDataset = (overrides: Partial<DashboardDataset> = {}): 
DashboardDataset => ({
+    isOwner: false,
+    ownerEmail: "[email protected]",
+    dataset: makeDataset(),
+    accessPrivilege: "NONE",
+    size: 0,
+    ...overrides,
+  });
+
+  const makeVersion = (overrides: Partial<DatasetVersion> = {}): 
DatasetVersion => ({
+    dvid: 1,
+    did: 5,
+    creatorUid: 9,
+    name: "v1",
+    versionHash: undefined,
+    creationTime: undefined,
+    fileNodes: undefined,
+    ...overrides,
+  });
+
+  const fileLeaf = (name: string, parentDir: string, size: number): 
DatasetFileNode => ({
+    name,
+    type: "file",
+    parentDir,
+    size,
+  });
+
+  const createComponent = (params: Record<string, unknown> = { did: 5 }): void 
=> {
+    TestBed.configureTestingModule({
+      imports: [DatasetDetailComponent, ...commonTestImports],
+      providers: [
+        { provide: ActivatedRoute, useValue: { params: of(params), data: 
of({}) } },
+        { provide: NzModalService, useValue: {} },
+        { provide: DatasetService, useValue: datasetServiceStub },
+        { provide: NotificationService, useValue: notificationServiceStub },
+        { provide: DownloadService, useValue: downloadServiceStub },
+        { provide: UserService, useClass: StubUserService },
+        { provide: HubService, useValue: hubServiceStub },
+        { provide: AdminSettingsService, useValue: adminSettingsServiceStub },
+        { provide: MarkdownService, useValue: { parse: vi.fn(() => "") } },
+        ...commonTestProviders,
+      ],
+    });
+    fixture = TestBed.createComponent(DatasetDetailComponent);
+    component = fixture.componentInstance;
+  };
+
+  // The StubUserService emits MOCK_USER in its own constructor, before the
+  // component subscribes, so currentUid starts undefined; re-emit to log in.
+  const login = (): void => {
+    (TestBed.inject(UserService) as unknown as 
StubUserService).userChangeSubject.next(MOCK_USER);
+  };
+
+  beforeEach(() => {
+    datasetServiceStub = {
+      getDataset: vi.fn(() => of(makeDashboardDataset())),
+      retrieveDatasetVersionList: vi.fn(() => of([])),
+      getDatasetCoverUrl: vi.fn(() => of({ url: "http://cover"; })),
+      retrieveDatasetVersionFileTree: vi.fn(() => of({ fileNodes: 
[fileLeaf("a.txt", "/root", 1)], size: 1 })),
+      createDatasetVersion: vi.fn(() => of(makeVersion())),
+      updateDatasetPublicity: vi.fn(() => of({})),
+      updateDatasetDownloadable: vi.fn(() => of({})),
+      updateDatasetCoverImage: vi.fn(() => of({})),
+      updateDatasetDescription: vi.fn(() => of({})),
+      deleteDatasetFile: vi.fn(() => of({})),
+      getDatasetDiff: vi.fn(() => of([])),
+      multipartUpload: vi.fn(() => of()),
+      finalizeMultipartUpload: vi.fn(() => of({})),
+    };
+    notificationServiceStub = { success: vi.fn(), error: vi.fn(), info: 
vi.fn() };
+    downloadServiceStub = {
+      downloadDatasetVersion: vi.fn(() => of(new Blob())),
+      downloadSingleFile: vi.fn(() => of(new Blob())),
+    };
+    hubServiceStub = {
+      getCounts: vi.fn(() => of([{ counts: { like: 0 } }])),
+      postView: vi.fn(() => of(0)),
+      isLiked: vi.fn(() => of([{ isLiked: false }])),
+      postLike: vi.fn(() => of(true)),
+      postUnlike: vi.fn(() => of(true)),
+    };
+    adminSettingsServiceStub = { getSetting: vi.fn(() => of("50")) };
+  });
+
+  describe("ngOnInit", () => {
+    it("loads info, versions, like and view counts but skips liked/upload 
settings without a current user", () => {
+      hubServiceStub.getCounts.mockReturnValue(of([{ counts: { like: 7 } }]));
+      hubServiceStub.postView.mockReturnValue(of(42));
+
+      createComponent({ did: 5 });
+      fixture.detectChanges();

Review Comment:
   Now sets `component.isLogin = false` explicitly before `detectChanges`, so 
the test exercises the genuine logged-out path instead of relying on 
StubUserService's emission quirk (8ea9ffd).



##########
frontend/src/app/dashboard/component/user/user-dataset/user-dataset-explorer/dataset-detail.component.spec.ts:
##########
@@ -334,3 +338,653 @@ describe("DatasetDetailComponent upload queue", () => {
     expect(component.pendingChangesCount).toBe(1);
   });
 });
+
+describe("DatasetDetailComponent behavior", () => {
+  let fixture: ComponentFixture<DatasetDetailComponent>;
+  let component: DatasetDetailComponent;
+
+  type MockService = Record<string, ReturnType<typeof vi.fn>>;
+  let datasetServiceStub: MockService;
+  let notificationServiceStub: MockService;
+  let downloadServiceStub: MockService;
+  let hubServiceStub: MockService;
+  let adminSettingsServiceStub: MockService;
+
+  const CREATION_TS = 1_700_000_000_000;
+
+  const makeDataset = (overrides: Partial<Dataset> = {}): Dataset => ({
+    did: 5,
+    ownerUid: 9,
+    name: "ds",
+    isPublic: false,
+    isDownloadable: true,
+    storagePath: undefined,
+    description: "desc",
+    creationTime: undefined,
+    coverImage: undefined,
+    ...overrides,
+  });
+
+  const makeDashboardDataset = (overrides: Partial<DashboardDataset> = {}): 
DashboardDataset => ({
+    isOwner: false,
+    ownerEmail: "[email protected]",
+    dataset: makeDataset(),
+    accessPrivilege: "NONE",
+    size: 0,
+    ...overrides,
+  });
+
+  const makeVersion = (overrides: Partial<DatasetVersion> = {}): 
DatasetVersion => ({
+    dvid: 1,
+    did: 5,
+    creatorUid: 9,
+    name: "v1",
+    versionHash: undefined,
+    creationTime: undefined,
+    fileNodes: undefined,
+    ...overrides,
+  });
+
+  const fileLeaf = (name: string, parentDir: string, size: number): 
DatasetFileNode => ({
+    name,
+    type: "file",
+    parentDir,
+    size,
+  });
+
+  const createComponent = (params: Record<string, unknown> = { did: 5 }): void 
=> {
+    TestBed.configureTestingModule({
+      imports: [DatasetDetailComponent, ...commonTestImports],
+      providers: [
+        { provide: ActivatedRoute, useValue: { params: of(params), data: 
of({}) } },
+        { provide: NzModalService, useValue: {} },
+        { provide: DatasetService, useValue: datasetServiceStub },
+        { provide: NotificationService, useValue: notificationServiceStub },
+        { provide: DownloadService, useValue: downloadServiceStub },
+        { provide: UserService, useClass: StubUserService },
+        { provide: HubService, useValue: hubServiceStub },
+        { provide: AdminSettingsService, useValue: adminSettingsServiceStub },
+        { provide: MarkdownService, useValue: { parse: vi.fn(() => "") } },
+        ...commonTestProviders,
+      ],
+    });
+    fixture = TestBed.createComponent(DatasetDetailComponent);
+    component = fixture.componentInstance;
+  };
+
+  // The StubUserService emits MOCK_USER in its own constructor, before the
+  // component subscribes, so currentUid starts undefined; re-emit to log in.
+  const login = (): void => {
+    (TestBed.inject(UserService) as unknown as 
StubUserService).userChangeSubject.next(MOCK_USER);
+  };
+
+  beforeEach(() => {
+    datasetServiceStub = {
+      getDataset: vi.fn(() => of(makeDashboardDataset())),
+      retrieveDatasetVersionList: vi.fn(() => of([])),
+      getDatasetCoverUrl: vi.fn(() => of({ url: "http://cover"; })),
+      retrieveDatasetVersionFileTree: vi.fn(() => of({ fileNodes: 
[fileLeaf("a.txt", "/root", 1)], size: 1 })),
+      createDatasetVersion: vi.fn(() => of(makeVersion())),
+      updateDatasetPublicity: vi.fn(() => of({})),
+      updateDatasetDownloadable: vi.fn(() => of({})),
+      updateDatasetCoverImage: vi.fn(() => of({})),
+      updateDatasetDescription: vi.fn(() => of({})),
+      deleteDatasetFile: vi.fn(() => of({})),
+      getDatasetDiff: vi.fn(() => of([])),
+      multipartUpload: vi.fn(() => of()),
+      finalizeMultipartUpload: vi.fn(() => of({})),
+    };
+    notificationServiceStub = { success: vi.fn(), error: vi.fn(), info: 
vi.fn() };
+    downloadServiceStub = {
+      downloadDatasetVersion: vi.fn(() => of(new Blob())),
+      downloadSingleFile: vi.fn(() => of(new Blob())),
+    };
+    hubServiceStub = {
+      getCounts: vi.fn(() => of([{ counts: { like: 0 } }])),
+      postView: vi.fn(() => of(0)),
+      isLiked: vi.fn(() => of([{ isLiked: false }])),
+      postLike: vi.fn(() => of(true)),
+      postUnlike: vi.fn(() => of(true)),
+    };
+    adminSettingsServiceStub = { getSetting: vi.fn(() => of("50")) };
+  });
+
+  describe("ngOnInit", () => {
+    it("loads info, versions, like and view counts but skips liked/upload 
settings without a current user", () => {
+      hubServiceStub.getCounts.mockReturnValue(of([{ counts: { like: 7 } }]));
+      hubServiceStub.postView.mockReturnValue(of(42));
+
+      createComponent({ did: 5 });
+      fixture.detectChanges();
+
+      expect(datasetServiceStub.getDataset).toHaveBeenCalled();
+      expect(datasetServiceStub.retrieveDatasetVersionList).toHaveBeenCalled();
+      expect(component.likeCount).toBe(7);
+      expect(component.viewCount).toBe(42);
+      expect(hubServiceStub.isLiked).not.toHaveBeenCalled();
+      expect(adminSettingsServiceStub.getSetting).not.toHaveBeenCalled();
+    });
+
+    it("fetches liked status and upload settings for a logged-in user", () => {
+      hubServiceStub.isLiked.mockReturnValue(of([{ isLiked: true }]));
+
+      createComponent({ did: 5 });
+      login();
+      fixture.detectChanges();
+
+      expect(hubServiceStub.isLiked).toHaveBeenCalled();
+      expect(component.isLiked).toBe(true);
+      expect(adminSettingsServiceStub.getSetting).toHaveBeenCalled();
+    });
+
+    it("makes no hub calls when the route carries no did", () => {
+      createComponent({});
+      component.ngOnInit();
+
+      expect(datasetServiceStub.getDataset).not.toHaveBeenCalled();
+      expect(hubServiceStub.getCounts).not.toHaveBeenCalled();
+      expect(hubServiceStub.postView).not.toHaveBeenCalled();
+    });
+  });
+
+  describe("retrieveDatasetInfo", () => {
+    it("maps dataset fields, formats numeric creation time, and resolves the 
cover image url", () => {
+      const dashboard = makeDashboardDataset({
+        isOwner: true,
+        ownerEmail: "[email protected]",
+        accessPrivilege: "WRITE",
+        dataset: makeDataset({
+          name: "N",
+          description: "D",
+          isPublic: true,
+          isDownloadable: false,
+          coverImage: "cover.png",
+          creationTime: CREATION_TS,
+        }),
+      });
+      datasetServiceStub.getDataset.mockReturnValue(of(dashboard));
+      datasetServiceStub.getDatasetCoverUrl.mockReturnValue(of({ url: 
"http://c"; }));
+
+      createComponent();
+      component.did = 5;
+      component.retrieveDatasetInfo();
+
+      expect(component.datasetName).toBe("N");
+      expect(component.datasetDescription).toBe("D");
+      expect(component.userDatasetAccessLevel).toBe("WRITE");
+      expect(component.datasetIsPublic).toBe(true);
+      expect(component.datasetIsDownloadable).toBe(false);
+      expect(component.ownerEmail).toBe("[email protected]");
+      expect(component.isOwner).toBe(true);
+      expect(component.coverImageUrl).toBe("http://c";);
+      expect(component.datasetCreationTime).toEqual(format(new 
Date(CREATION_TS), "MM/dd/yyyy HH:mm:ss"));
+      expect(component.datasetCreationTime).toMatch(/^\d{2}\/\d{2}\/\d{4} 
\d{2}:\d{2}:\d{2}$/);
+    });
+
+    it("nulls the cover image url when its retrieval fails", () => {
+      datasetServiceStub.getDataset.mockReturnValue(
+        of(makeDashboardDataset({ dataset: makeDataset({ coverImage: "c.png" 
}) }))
+      );
+      datasetServiceStub.getDatasetCoverUrl.mockReturnValue(throwError(() => 
new Error("boom")));
+
+      createComponent();
+      component.did = 5;
+      component.coverImageUrl = "stale";
+      component.retrieveDatasetInfo();
+
+      expect(component.coverImageUrl).toBeNull();
+    });
+
+    it("leaves the cover image url null and skips the cover fetch when there 
is no cover image", () => {
+      datasetServiceStub.getDataset.mockReturnValue(
+        of(makeDashboardDataset({ dataset: makeDataset({ coverImage: undefined 
}) }))
+      );
+
+      createComponent();
+      component.did = 5;
+      component.coverImageUrl = "stale";
+      component.retrieveDatasetInfo();
+
+      expect(component.coverImageUrl).toBeNull();
+      expect(datasetServiceStub.getDatasetCoverUrl).not.toHaveBeenCalled();
+    });
+  });
+
+  describe("retrieveDatasetVersionList", () => {
+    it("selects the first version and delegates to onVersionSelected when the 
list is non-empty", () => {
+      const v1 = makeVersion({ dvid: 10, name: "v10" });
+      const v2 = makeVersion({ dvid: 9, name: "v9" });
+      datasetServiceStub.retrieveDatasetVersionList.mockReturnValue(of([v1, 
v2]));
+
+      createComponent();
+      component.did = 5;
+      const spy = vi.spyOn(component, "onVersionSelected");
+      component.retrieveDatasetVersionList();
+
+      expect(component.versions).toEqual([v1, v2]);
+      expect(component.selectedVersion).toEqual(v1);
+      expect(spy).toHaveBeenCalledWith(v1);
+    });
+
+    it("makes no selection when the version list is empty", () => {
+      datasetServiceStub.retrieveDatasetVersionList.mockReturnValue(of([]));
+
+      createComponent();
+      component.did = 5;
+      component.selectedVersion = undefined;
+      const spy = vi.spyOn(component, "onVersionSelected");
+      component.retrieveDatasetVersionList();
+
+      expect(component.versions).toEqual([]);
+      expect(component.selectedVersion).toBeUndefined();
+      expect(spy).not.toHaveBeenCalled();
+    });
+  });
+
+  describe("onVersionSelected", () => {
+    it("walks nested directories to the first file leaf and loads it", () => {
+      const leaf = fileLeaf("c.txt", "/root/a", 42);
+      const tree: DatasetFileNode[] = [{ name: "a", type: "directory", 
parentDir: "/root", children: [leaf] }];
+      datasetServiceStub.retrieveDatasetVersionFileTree.mockReturnValue(of({ 
fileNodes: tree, size: 100 }));
+
+      createComponent();
+      component.did = 5;
+      component.onVersionSelected(makeVersion({ dvid: 2, creationTime: 
CREATION_TS }));
+
+      expect(component.fileTreeNodeList).toEqual(tree);
+      expect(component.currentDatasetVersionSize).toBe(100);
+      
expect(component.currentDisplayedFileName).toBe(getFullPathFromDatasetFileNode(leaf));
+      expect(component.currentFileSize).toBe(42);
+      
expect(component.selectedVersionCreationTime).toMatch(/^\d{2}\/\d{2}\/\d{4} 
\d{2}:\d{2}:\d{2}$/);
+    });
+
+    it("does not fetch a file tree for a version without a dvid", () => {
+      createComponent();
+      component.did = 5;
+      component.onVersionSelected(makeVersion({ dvid: undefined }));
+
+      
expect(datasetServiceStub.retrieveDatasetVersionFileTree).not.toHaveBeenCalled();
+    });
+  });
+
+  describe("isDownloadAllowed and userHasWriteAccess", () => {
+    beforeEach(() => createComponent());
+
+    it("always allows the owner to download, even when the dataset is not 
downloadable", () => {
+      component.isOwner = true;
+      component.datasetIsDownloadable = false;
+      expect(component.isDownloadAllowed()).toBe(true);
+    });
+
+    it("allows a non-owner to download a public downloadable dataset without 
explicit access", () => {
+      component.isOwner = false;
+      component.datasetIsDownloadable = true;
+      component.datasetIsPublic = true;
+      component.userDatasetAccessLevel = "NONE";
+      expect(component.isDownloadAllowed()).toBe(true);
+    });
+
+    it("blocks a non-owner from a private downloadable dataset without 
access", () => {
+      component.isOwner = false;
+      component.datasetIsDownloadable = true;
+      component.datasetIsPublic = false;
+      component.userDatasetAccessLevel = "NONE";
+      expect(component.isDownloadAllowed()).toBe(false);
+    });
+
+    it("blocks download when the dataset is not downloadable", () => {
+      component.isOwner = false;
+      component.datasetIsDownloadable = false;
+      component.datasetIsPublic = true;
+      expect(component.isDownloadAllowed()).toBe(false);
+    });
+
+    it("reports write access only for the WRITE privilege", () => {
+      component.userDatasetAccessLevel = "WRITE";
+      expect(component.userHasWriteAccess()).toBe(true);
+      component.userDatasetAccessLevel = "READ";
+      expect(component.userHasWriteAccess()).toBe(false);
+      component.userDatasetAccessLevel = "NONE";
+      expect(component.userHasWriteAccess()).toBe(false);
+    });
+  });
+
+  describe("publicity and downloadable toggles", () => {
+    it("marks the dataset public and toasts on success", () => {
+      createComponent();
+      component.did = 5;
+      component.datasetName = "MyDS";
+      component.onPublicStatusChange(true);
+
+      expect(component.datasetIsPublic).toBe(true);
+      expect(notificationServiceStub.success).toHaveBeenCalledWith("Dataset 
MyDS is now public");
+    });
+
+    it("keeps the public flag and toasts an error when the publicity update 
fails", () => {
+      datasetServiceStub.updateDatasetPublicity.mockReturnValue(throwError(() 
=> new Error("boom")));
+      createComponent();
+      component.did = 5;
+      component.datasetIsPublic = false;
+      component.onPublicStatusChange(true);
+
+      expect(component.datasetIsPublic).toBe(false);
+      expect(notificationServiceStub.error).toHaveBeenCalledWith("Fail to 
change the dataset publicity");
+    });
+
+    it("marks downloads not-allowed and toasts on success", () => {
+      createComponent();
+      component.did = 5;
+      component.onDownloadableStatusChange(false);
+
+      expect(component.datasetIsDownloadable).toBe(false);
+      expect(notificationServiceStub.success).toHaveBeenCalledWith("Dataset 
downloads are now not allowed");
+    });
+
+    it("keeps the downloadable flag and toasts an error when the update 
fails", () => {
+      
datasetServiceStub.updateDatasetDownloadable.mockReturnValue(throwError(() => 
new Error("boom")));
+      createComponent();
+      component.did = 5;
+      component.datasetIsDownloadable = true;
+      component.onDownloadableStatusChange(false);
+
+      expect(component.datasetIsDownloadable).toBe(true);
+      expect(notificationServiceStub.error).toHaveBeenCalledWith("Failed to 
change the dataset download permission");
+    });
+  });
+
+  describe("onClickOpenVersionCreator", () => {
+    it("creates a version, clears the name, refreshes the list and emits a 
change on success", () => {
+      
datasetServiceStub.createDatasetVersion.mockReturnValue(of(makeVersion()));
+      datasetServiceStub.retrieveDatasetVersionList.mockReturnValue(of([]));
+      createComponent();
+      component.did = 5;
+      component.versionName = "v2";
+      const emit = vi.fn();
+      component.userMakeChanges.subscribe(emit);
+
+      component.onClickOpenVersionCreator();
+
+      expect(datasetServiceStub.createDatasetVersion).toHaveBeenCalledWith(5, 
"v2");
+      expect(notificationServiceStub.success).toHaveBeenCalledWith("Version 
Created");
+      expect(component.versionName).toBe("");
+      expect(component.isCreatingVersion).toBe(false);
+      expect(datasetServiceStub.retrieveDatasetVersionList).toHaveBeenCalled();
+      expect(emit).toHaveBeenCalled();
+    });
+
+    it("surfaces the backend message and resets the in-progress flag on 
failure", () => {
+      datasetServiceStub.createDatasetVersion.mockReturnValue(throwError(() => 
({ error: { message: "boom" } })));
+      createComponent();
+      component.did = 5;
+      component.versionName = "v2";
+
+      component.onClickOpenVersionCreator();
+
+      expect(notificationServiceStub.error).toHaveBeenCalledWith("Version 
creation failed: boom");
+      expect(component.isCreatingVersion).toBe(false);
+    });
+
+    it("ignores a second click while a version creation is already in 
progress", () => {
+      datasetServiceStub.createDatasetVersion.mockReturnValue(new Subject());
+      createComponent();
+      component.did = 5;
+
+      component.onClickOpenVersionCreator();
+      component.onClickOpenVersionCreator();
+
+      expect(datasetServiceStub.createDatasetVersion).toHaveBeenCalledTimes(1);
+      expect(component.isCreatingVersion).toBe(true);
+    });
+  });
+
+  describe("downloads", () => {
+    it("downloads the selected version as a zip when did and dvid are 
present", () => {
+      createComponent();
+      component.did = 5;
+      component.datasetName = "DS";
+      component.selectedVersion = makeVersion({ dvid: 3, name: "v3" });
+
+      component.onClickDownloadVersionAsZip();
+
+      
expect(downloadServiceStub.downloadDatasetVersion).toHaveBeenCalledWith(5, 3, 
"DS", "v3");
+    });
+
+    it("does not download a zip when no version is selected", () => {
+      createComponent();
+      component.did = 5;
+      component.selectedVersion = undefined;
+
+      component.onClickDownloadVersionAsZip();
+
+      
expect(downloadServiceStub.downloadDatasetVersion).not.toHaveBeenCalled();
+    });
+
+    it("uses the public endpoint to download the current file for a public 
non-owner dataset", () => {
+      createComponent();
+      component.did = 5;
+      component.selectedVersion = makeVersion({ dvid: 3 });
+      component.datasetIsPublic = true;
+      component.isOwner = false;
+      component.currentDisplayedFileName = "/a/b/c.txt";
+
+      component.onClickDownloadCurrentFile();
+
+      
expect(downloadServiceStub.downloadSingleFile).toHaveBeenCalledWith("/a/b/c.txt",
 false);
+    });
+
+    it("uses the authenticated endpoint to download the current file for the 
owner", () => {
+      createComponent();
+      component.did = 5;
+      component.selectedVersion = makeVersion({ dvid: 3 });
+      component.datasetIsPublic = true;
+      component.isOwner = true;
+      component.currentDisplayedFileName = "/a/b/c.txt";
+
+      component.onClickDownloadCurrentFile();
+
+      
expect(downloadServiceStub.downloadSingleFile).toHaveBeenCalledWith("/a/b/c.txt",
 true);
+    });
+
+    it("does not download the current file without a selected version dvid", 
() => {
+      createComponent();
+      component.did = 5;
+      component.selectedVersion = undefined;
+
+      component.onClickDownloadCurrentFile();
+
+      expect(downloadServiceStub.downloadSingleFile).not.toHaveBeenCalled();
+    });
+  });
+
+  describe("staged objects and view flags", () => {
+    beforeEach(() => createComponent());
+
+    it("tracks the pending-change count from staged objects", () => {
+      const staged: DatasetStagedObject[] = [
+        { path: "a", pathType: "file", diffType: "added", sizeBytes: 1 },
+        { path: "b", pathType: "file", diffType: "added", sizeBytes: 1 },
+      ];
+      component.onStagedObjectsUpdated(staged);
+      expect(component.pendingChangesCount).toBe(2);
+      expect(component.userHasPendingChanges).toBe(true);
+
+      component.onStagedObjectsUpdated([]);
+      expect(component.pendingChangesCount).toBe(0);
+      expect(component.userHasPendingChanges).toBe(false);
+    });
+
+    it("toggles the maximize, right-bar and precise-view-count flags", () => {
+      expect(component.isMaximized).toBe(false);
+      component.onClickScaleTheView();
+      expect(component.isMaximized).toBe(true);
+
+      expect(component.isRightBarCollapsed).toBe(false);
+      component.onClickHideRightBar();
+      expect(component.isRightBarCollapsed).toBe(true);
+
+      expect(component.displayPreciseViewCount).toBe(false);
+      component.changeViewDisplayStyle();
+      expect(component.displayPreciseViewCount).toBe(true);
+    });
+  });
+
+  describe("toggleLike", () => {
+    it("unlikes and decrements the like count when currently liked", () => {
+      hubServiceStub.postUnlike.mockReturnValue(of(true));
+      hubServiceStub.getCounts.mockReturnValue(of([{ counts: { like: 4 } }]));
+      createComponent();
+      component.did = 5;
+      component.currentUid = MOCK_USER.uid;
+      component.isLiked = true;
+      component.likeCount = 5;
+
+      component.toggleLike();
+
+      expect(hubServiceStub.postUnlike).toHaveBeenCalled();
+      expect(component.isLiked).toBe(false);
+      expect(component.likeCount).toBe(4);
+    });
+
+    it("likes and increments the like count when not currently liked", () => {
+      hubServiceStub.postLike.mockReturnValue(of(true));
+      hubServiceStub.getCounts.mockReturnValue(of([{ counts: { like: 6 } }]));
+      createComponent();
+      component.did = 5;
+      component.currentUid = MOCK_USER.uid;
+      component.isLiked = false;
+      component.likeCount = 5;
+
+      component.toggleLike();
+
+      expect(hubServiceStub.postLike).toHaveBeenCalled();
+      expect(component.isLiked).toBe(true);
+      expect(component.likeCount).toBe(6);
+    });
+
+    it("does nothing when no user is logged in", () => {
+      createComponent();
+      component.did = 5;
+      component.currentUid = undefined;
+
+      component.toggleLike();
+
+      expect(hubServiceStub.postLike).not.toHaveBeenCalled();
+      expect(hubServiceStub.postUnlike).not.toHaveBeenCalled();
+    });
+  });
+
+  describe("cover image and description persistence", () => {
+    it("refreshes the cover url and toasts success after setting a cover 
image", () => {
+      datasetServiceStub.updateDatasetCoverImage.mockReturnValue(of({}));
+      datasetServiceStub.getDatasetCoverUrl.mockReturnValue(of({ url: 
"http://new"; }));
+      createComponent();
+      component.did = 5;
+      component.selectedVersion = makeVersion({ name: "v1" });
+
+      component.onSetCoverImage("img.png");
+
+      
expect(datasetServiceStub.updateDatasetCoverImage).toHaveBeenCalledWith(5, 
"v1/img.png");
+      expect(component.coverImageUrl).toBe("http://new";);
+      expect(notificationServiceStub.success).toHaveBeenCalledWith("Cover 
image updated.");
+    });
+
+    it("surfaces the backend message when setting the cover image fails", () 
=> {
+      datasetServiceStub.updateDatasetCoverImage.mockReturnValue(
+        throwError(() => new HttpErrorResponse({ error: { message: "nope" }, 
status: 400 }))
+      );
+      createComponent();
+      component.did = 5;
+      component.selectedVersion = makeVersion({ name: "v1" });
+
+      component.onSetCoverImage("img.png");
+
+      expect(notificationServiceStub.error).toHaveBeenCalledWith("nope");
+    });
+
+    it("does nothing when there is no selected version to attach the cover 
to", () => {
+      createComponent();
+      component.did = 5;
+      component.selectedVersion = undefined;
+
+      component.onSetCoverImage("img.png");
+
+      
expect(datasetServiceStub.updateDatasetCoverImage).not.toHaveBeenCalled();
+    });
+
+    it("persists a changed description and updates the field", () => {
+      datasetServiceStub.updateDatasetDescription.mockReturnValue(of({}));
+      createComponent();
+      component.did = 5;
+      component.datasetDescription = "old";
+
+      component.onDatasetDescriptionChange("new");
+
+      
expect(datasetServiceStub.updateDatasetDescription).toHaveBeenCalledWith(5, 
"new");
+      expect(component.datasetDescription).toBe("new");
+    });
+
+    it("skips the persistence call when the description is unchanged", () => {
+      createComponent();
+      component.did = 5;
+      component.datasetDescription = "same";
+
+      component.onDatasetDescriptionChange("same");
+
+      
expect(datasetServiceStub.updateDatasetDescription).not.toHaveBeenCalled();
+    });
+
+    it("reverts the description and toasts an error when persistence fails", 
() => {
+      
datasetServiceStub.updateDatasetDescription.mockReturnValue(throwError(() => 
new Error("boom")));
+      createComponent();
+      component.did = 5;
+      component.datasetDescription = "old";
+
+      component.onDatasetDescriptionChange("new");
+
+      expect(component.datasetDescription).toBe("old");
+      expect(notificationServiceStub.error).toHaveBeenCalledWith("Failed to 
update dataset description");
+    });
+  });
+
+  describe("copyCurrentFilePath", () => {
+    let originalClipboard: Clipboard;
+    let writeText: ReturnType<typeof vi.fn>;
+
+    beforeEach(() => {
+      originalClipboard = navigator.clipboard;
+      writeText = vi.fn().mockResolvedValue(undefined);
+      Object.defineProperty(navigator, "clipboard", { value: { writeText }, 
configurable: true });
+      createComponent();
+    });
+
+    afterEach(() => {
+      Object.defineProperty(navigator, "clipboard", { value: 
originalClipboard, configurable: true });
+    });

Review Comment:
   Now captures the original own-property descriptor via 
`Object.getOwnPropertyDescriptor(navigator, "clipboard")` and, in afterEach, 
restores that descriptor — or `delete`s the stubbed property when `clipboard` 
wasn't an own property to begin with (as under jsdom), so nothing leaks into 
other files (8ea9ffd).



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to