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


##########
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:
   This spec intends to cover the anonymous/no-user path, but with 
StubUserService the component’s `isLogin` defaults to true (StubUserService 
always has a user). That means `retrieveDatasetInfo()` / 
`retrieveDatasetVersionList()` are still invoked with `isLogin=true`, which 
doesn’t match the real logged-out state (UserService replays `undefined` and 
`isLogin` would be false). Consider explicitly setting `component.isLogin = 
false` in this test so the exercised path matches the intended scenario and is 
less coupled to the StubUserService emission quirk.



##########
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:
   The clipboard stub restores only the *value*, not the original property 
shape. If `navigator.clipboard` was not an own property (or didn’t exist), the 
afterEach leaves an own `clipboard` property behind (value `undefined`), which 
can leak into other test files. Track whether the property originally existed 
on `navigator` and delete the stubbed property when it didn’t.



-- 
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