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


The following commit(s) were added to refs/heads/main by this push:
     new ae3ad45c45 test(frontend): render the dataset list item's permission 
gates (#7443)
ae3ad45c45 is described below

commit ae3ad45c45712e22625a5e30516cc1a31df8c5de
Author: Xinyuan Lin <[email protected]>
AuthorDate: Sun Aug 9 16:57:35 2026 -0700

    test(frontend): render the dataset list item's permission gates (#7443)
    
    ### What changes were proposed in this PR?
    
    Whether this row offers any editing is decided in the template by
    **two** conditions, not one:
    
    ```html
    *ngIf="editable && entry.accessPrivilege === 'WRITE'"
    ```
    
    The list being editable is not on its own permission to change someone
    else's dataset. The existing suite exercises the component's methods and
    never renders, so neither condition was pinned.
    
    Adds 8 tests covering both halves independently — a reader on an
    editable list gets no rename or add-description control, and neither
    does a writer on a non-editable list — plus the same pair guarding the
    inline description, the owner and shared-access markers being mutually
    exclusive, the shared marker naming the privilege held, and the rename
    input being seeded from the dataset's name.
    
    **Verified by mutation**, all reverted (template diff empty):
    
    | Mutation | Result |
    |---|---|
    | rename gate drops the WRITE check | red |
    | rename gate drops the editable check | red |
    | description gate drops the WRITE check | red |
    | inline description gate drops the WRITE check | red |
    | show the owner marker to everyone | red |
    | show the shared marker to the owner too | red |
    | invert the name / edit-input branch | red |
    | seed the rename input from the description | red |
    
    Testing both halves separately is the point: dropping either condition
    alone still leaves a single-condition test passing.
    
    The shared-marker mutation **survived its first run** — the owner test
    asserted its own marker was present but not that the shared one was
    absent. It is now exclusive.
    
    No production file is touched.
    
    ### Any related issues, documentation, discussions?
    
    Closes #7440
    
    ### How was this PR tested?
    
    ```
    npx ng test --watch=false 
--include="**/user-dataset-list-item.component.spec.ts"
    ```
    
    ```
     Test Files  1 passed (1)
          Tests  28 passed (28)
    ```
    
    8 new on top of the existing 20. `yarn format:ci` passes.
    
    ### Was this PR authored or co-authored using generative AI tooling?
    
    Generated-by: Claude Code (Opus 5)
---
 .../user-dataset-list-item.component.spec.ts       | 113 +++++++++++++++++++++
 1 file changed, 113 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
index 513d803b87..b023f10f3f 100644
--- 
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
@@ -19,10 +19,12 @@
 
 import { Component, EventEmitter, ViewChild } from "@angular/core";
 import { ComponentFixture, TestBed } from "@angular/core/testing";
+import { By } from "@angular/platform-browser";
 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 { NzTooltipDirective } from "ng-zorro-antd/tooltip";
 import { of, throwError } from "rxjs";
 import type { Mocked } from "vitest";
 import { UserDatasetListItemComponent } from 
"./user-dataset-list-item.component";
@@ -308,4 +310,115 @@ describe("UserDatasetListItemComponent", () => {
       expect(component.refresh).toBeInstanceOf(EventEmitter);
     });
   });
+  /**
+   * Whether this row offers any editing is decided in the template, and by 
TWO conditions rather
+   * than one: the list must be editable AND the viewer must hold WRITE on the 
dataset. The suite
+   * above exercises the component's methods and never renders, so neither 
condition was pinned.
+   */
+  describe("rendered row", () => {
+    /** Re-renders the host with the given entry and list-level editability. */
+    function render(over: Partial<DashboardDataset> = {}, editable = true): 
HTMLElement {
+      fixture.componentInstance.entry = makeEntry(over);
+      fixture.componentInstance.editable = editable;
+      fixture.detectChanges();
+      component = fixture.componentInstance.inner;
+      return fixture.nativeElement as HTMLElement;
+    }
+
+    /** Titles of every tooltip on the row; interpolated ones never reach the 
DOM as attributes. */
+    function tooltipTitles(): unknown[] {
+      return fixture.debugElement
+        .queryAll(By.directive(NzTooltipDirective))
+        .map(d => (d.injector.get(NzTooltipDirective) as 
NzTooltipDirective).directiveTitle);
+    }
+
+    function hasTooltip(pred: (t: string) => boolean): boolean {
+      return tooltipTitles().some(t => typeof t === "string" && pred(t));
+    }
+
+    it("offers the editing controls to a writer on an editable list", () => {
+      render({ accessPrivilege: "WRITE" }, true);
+
+      expect(hasTooltip(t => t === "Customize Dataset Name")).toBe(true);
+      expect(hasTooltip(t => t === "Add Description")).toBe(true);
+    });
+
+    it("withholds them from a reader, even on an editable list", () => {
+      // READ access must not be offered a rename it cannot persist; the list 
being editable is not
+      // on its own permission to change someone else's dataset.
+      render({ accessPrivilege: "READ" }, true);
+
+      expect(hasTooltip(t => t === "Customize Dataset Name")).toBe(false);
+      expect(hasTooltip(t => t === "Add Description")).toBe(false);
+    });
+
+    it("withholds them on a non-editable list, even from a writer", () => {
+      // Both controls carry the same pair of conditions, so both are 
asserted: checking only the
+      // rename would let the add-description button lose its `editable` half 
unnoticed.
+      render({ accessPrivilege: "WRITE" }, false);
+
+      expect(hasTooltip(t => t === "Customize Dataset Name")).toBe(false);
+      expect(hasTooltip(t => t === "Add Description")).toBe(false);
+    });
+
+    it("applies the same pair of conditions to the inline description", () => {
+      render({ accessPrivilege: "READ" }, true);
+
+      (fixture.nativeElement as 
HTMLElement).querySelector<HTMLElement>(".dataset-description-label")?.click();
+      fixture.detectChanges();
+
+      expect(component.editingDescription).toBe(false);
+    });
+
+    it("keeps the inline description shut on a non-editable list, even for a 
writer", () => {
+      // The other half of the same `&&`. Without this case the gate could be 
reduced to the
+      // privilege check alone and every remaining test would still pass.
+      render({ accessPrivilege: "WRITE" }, false);
+
+      (fixture.nativeElement as 
HTMLElement).querySelector<HTMLElement>(".dataset-description-label")?.click();
+      fixture.detectChanges();
+
+      expect(component.editingDescription).toBe(false);
+    });
+
+    it("opens the inline description for a writer", () => {
+      render({ accessPrivilege: "WRITE" }, true);
+
+      (fixture.nativeElement as 
HTMLElement).querySelector<HTMLElement>(".dataset-description-label")?.click();
+      fixture.detectChanges();
+
+      expect(component.editingDescription).toBe(true);
+    });
+
+    it("marks a dataset the viewer owns, and only that marker", () => {
+      // The two markers are mutually exclusive; showing both would tell an 
owner their own dataset
+      // had been shared with them.
+      render({ isOwner: true, accessPrivilege: "WRITE" });
+
+      expect(hasTooltip(t => t === "You are the owner")).toBe(true);
+      expect(hasTooltip(t => t.endsWith(" Access"))).toBe(false);
+    });
+
+    it("tells a non-owner what access they hold instead", () => {
+      // The marker interpolates the privilege, so a reader and a writer are 
told different things.
+      render({ isOwner: false, accessPrivilege: "READ" });
+
+      expect(hasTooltip(t => t === "You are the owner")).toBe(false);
+      expect(hasTooltip(t => t === "READ Access")).toBe(true);
+    });
+
+    it("swaps the name for an input once renaming starts", () => {
+      const el = render({ accessPrivilege: "WRITE" }, true);
+      expect(el.querySelector("nz-list-item-meta-title input")).toBeNull();
+
+      component.editingName = true;
+      fixture.detectChanges();
+
+      const input = (fixture.nativeElement as 
HTMLElement).querySelector<HTMLInputElement>(
+        "nz-list-item-meta-title input"
+      )!;
+      expect(input).not.toBeNull();
+      expect(input.value).toBe(component.dataset.name);
+    });
+  });
 });

Reply via email to