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

commit 69c7391a5f613b165ce7276f81811a98de48c442
Author: Xinyuan Lin <[email protected]>
AuthorDate: Sat Aug 8 00:24:55 2026 -0700

    test(frontend): render the dataset page's view toggle and results wiring 
(#7417)
    
    ### What changes were proposed in this PR?
    
    `UserDatasetComponent`'s spec constructs the component with `new`, so
    its template had never been rendered and sat at 0%. That 0% is simply
    "never mounted" — not the instrumentation problem some other templates
    have.
    
    Adds 8 tests that mount it for real:
    
    - the card/list toggle, and which button carries the `primary` highlight
    — the only way the user can tell which view they are in
    - the preference surviving into a freshly created component through
    `localStorage`
    - the view mode, `editable` and `isPrivateSearch` flags handed down to
    `texera-search-results`
    - `(sortMethodChange)="sortMethod = $event; search()"` — an inline
    statement doing two things, where dropping either leaves the list in the
    previous order
    - the execution-time sort options being withheld on a page whose entries
    never execute
    - the create button being wired
    
    **Verified by mutation**, all reverted (production diff empty):
    
    | Mutation | Result |
    |---|---|
    | pin the list button's highlight on | red |
    | key the card highlight off the wrong mode | red |
    | stop passing the view mode down | red |
    | mark the results list read-only | red |
    | turn off the private-search scope | red |
    | drop `search()` from the sort handler | red |
    | drop the assignment from the sort handler | red |
    | offer the execution-time sort option | red |
    | unwire the create button | red |
    | stop persisting the view preference | red |
    
    Three findings from getting it to run, all commented in the spec:
    
    - `viewType` defaults to **card**, not list, and is seeded from
    `localStorage` at construction — so it is cleared per test, or a view
    chosen by one test leaks into the next. That persistence turned out to
    be worth a test of its own.
    - The sort button renders its own `<button>` into the same
    `nz-space-compact`, so a positional selector picks that up first. The
    view buttons are selected by their `title` attribute instead.
    - ng-zorro defaults to `zh-cn` and throws `NG0701` without locale data;
    the TestBed provides `{ provide: NZ_I18N, useValue: en_US }`, matching
    the sibling dashboard specs.
    
    No production file is touched.
    
    ### Any related issues, documentation, discussions?
    
    Closes #7414
    
    ### How was this PR tested?
    
    ```
    npx ng test --watch=false --include="**/user-dataset.component.spec.ts"
    ```
    
    ```
     Test Files  1 passed (1)
          Tests  26 passed (26)
    ```
    
    8 new on top of the existing 18. `yarn format:ci` passes.
    
    ### Was this PR authored or co-authored using generative AI tooling?
    
    Generated-by: Claude Code (Opus 5)
---
 .../user-dataset/user-dataset.component.spec.ts    | 153 +++++++++++++++++++++
 1 file changed, 153 insertions(+)

diff --git 
a/frontend/src/app/dashboard/component/user/user-dataset/user-dataset.component.spec.ts
 
b/frontend/src/app/dashboard/component/user/user-dataset/user-dataset.component.spec.ts
index 1806e51735..1ccd5918fb 100644
--- 
a/frontend/src/app/dashboard/component/user/user-dataset/user-dataset.component.spec.ts
+++ 
b/frontend/src/app/dashboard/component/user/user-dataset/user-dataset.component.spec.ts
@@ -18,6 +18,20 @@
  */
 
 import { of, Subject } from "rxjs";
+import { ComponentFixture, TestBed } from "@angular/core/testing";
+import { By } from "@angular/platform-browser";
+import { provideRouter } from "@angular/router";
+import { NzModalService } from "ng-zorro-antd/modal";
+import { NzMessageService } from "ng-zorro-antd/message";
+import { en_US, NZ_I18N } from "ng-zorro-antd/i18n";
+import { UserService } from "../../../../common/service/user/user.service";
+import { StubUserService } from 
"../../../../common/service/user/stub-user.service";
+import { SearchService } from "../../../service/user/search.service";
+import { DatasetService } from "../../../service/user/dataset/dataset.service";
+import { SearchResultsComponent } from 
"../search-results/search-results.component";
+import { SortButtonComponent } from "../sort-button/sort-button.component";
+import { commonTestImports, commonTestProviders } from 
"../../../../common/testing/test-utils";
+
 import { UserDatasetComponent } from "./user-dataset.component";
 import { USER_DATASET } from "../../../../app-routing.constant";
 import { UserDatasetVersionCreatorComponent } from 
"./user-dataset-explorer/user-dataset-version-creator/user-dataset-version-creator.component";
@@ -368,3 +382,142 @@ describe("UserDatasetComponent", () => {
     });
   });
 });
+/**
+ * The existing suite constructs the component directly, so its template has 
never been rendered.
+ * These tests mount it for real: the view toggle, the sort wiring and the 
bindings handed to the
+ * results list all live only in the template.
+ */
+/** Mirrors UserDatasetComponent's private static VIEW_MODE_STORAGE_KEY. */
+const VIEW_MODE_STORAGE_KEY = "texera.userDataset.viewMode";
+
+describe("UserDatasetComponent rendering", () => {
+  let fixture: ComponentFixture<UserDatasetComponent>;
+  let component: UserDatasetComponent;
+  let searchSpy: ReturnType<typeof vi.fn>;
+
+  beforeEach(async () => {
+    // viewType is seeded from localStorage at construction, so a view chosen 
by an earlier test
+    // would leak into this one. Only this component's key is removed, so 
nothing else in the
+    // shared jsdom store is disturbed.
+    localStorage.removeItem(VIEW_MODE_STORAGE_KEY);
+    searchSpy = vi.fn(() => of({ entries: [], more: false, hasMismatch: false 
}));
+    await TestBed.configureTestingModule({
+      imports: [UserDatasetComponent, ...commonTestImports],
+      providers: [
+        { provide: NzModalService, useValue: { create: vi.fn() } },
+        { provide: UserService, useClass: StubUserService },
+        { provide: SearchService, useValue: { executeSearch: searchSpy } },
+        { provide: DatasetService, useValue: { deleteDatasets: vi.fn(() => 
of({} as Response)) } },
+        { provide: NzMessageService, useValue: { warning: vi.fn() } },
+        // ng-zorro defaults to zh-cn and throws NG0701 without locale data; 
the app registers en_US.
+        { provide: NZ_I18N, useValue: en_US },
+        provideRouter([]),
+        ...commonTestProviders,
+      ],
+    }).compileComponents();
+
+    fixture = TestBed.createComponent(UserDatasetComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  afterEach(() => {
+    // Also on the way out: the persistence test leaves a chosen view behind, 
and this key is
+    // shared with the suite above.
+    localStorage.removeItem(VIEW_MODE_STORAGE_KEY);
+    vi.restoreAllMocks();
+  });
+
+  /**
+   * The two view-mode buttons, found by title: the sort button renders its 
own <button> into the
+   * same nz-space-compact, so a positional selector picks that up first.
+   */
+  function viewButtons(): { list: HTMLButtonElement; card: HTMLButtonElement } 
{
+    const host = fixture.nativeElement as HTMLElement;
+    const list = host.querySelector<HTMLButtonElement>('button[title="List 
View"]');
+    const card = host.querySelector<HTMLButtonElement>('button[title="Card 
View"]');
+    // Named up front so a renamed title fails as a missing button rather than 
a null dereference
+    // three lines later.
+    expect(list, 'no button titled "List View"').not.toBeNull();
+    expect(card, 'no button titled "Card View"').not.toBeNull();
+    return { list: list!, card: card! };
+  }
+
+  it("starts in card view with only that button highlighted", () => {
+    // nz-button renders nzType="primary" as ant-btn-primary; the highlight is 
how the user can tell
+    // which view they are in, so it has to follow viewType rather than being 
fixed.
+    const { list, card } = viewButtons();
+
+    expect(component.viewType).toBe("card");
+    expect(card.classList).toContain("ant-btn-primary");
+    expect(list.classList).not.toContain("ant-btn-primary");
+  });
+
+  it("moves the highlight when the list view is chosen", () => {
+    viewButtons().list.click();
+    fixture.detectChanges();
+
+    const { list, card } = viewButtons();
+    expect(component.viewType).toBe("list");
+    expect(list.classList).toContain("ant-btn-primary");
+    expect(card.classList).not.toContain("ant-btn-primary");
+  });
+
+  it("remembers the chosen view for the next visit", () => {
+    // The preference is persisted, so a fresh component picks it back up.
+    viewButtons().list.click();
+
+    const reopened = TestBed.createComponent(UserDatasetComponent);
+    expect(reopened.componentInstance.viewType).toBe("list");
+  });
+
+  it("passes the chosen view down to the results list", () => {
+    const results = 
fixture.debugElement.query(By.directive(SearchResultsComponent)).componentInstance;
+    expect(results.viewMode).toBe("card");
+
+    component.setViewType("list");
+    fixture.detectChanges();
+
+    
expect(fixture.debugElement.query(By.directive(SearchResultsComponent)).componentInstance.viewMode).toBe("list");
+  });
+
+  it("marks the results list as editable and private", () => {
+    // This is the user's own dataset page, so entries are editable and the 
search is scoped to them.
+    const results = 
fixture.debugElement.query(By.directive(SearchResultsComponent)).componentInstance;
+
+    expect(results.editable).toBe(true);
+    expect(results.isPrivateSearch).toBe(true);
+  });
+
+  it("re-runs the search when the sort method changes", () => {
+    // The template statement does two things — assign then search — and 
dropping either leaves the
+    // list showing results in the previous order.
+    const sortButton = 
fixture.debugElement.query(By.directive(SortButtonComponent));
+    searchSpy.mockClear();
+
+    sortButton.componentInstance.sortMethodChange.emit(SortMethod.NameAsc);
+    fixture.detectChanges();
+
+    expect(component.sortMethod).toBe(SortMethod.NameAsc);
+    expect(searchSpy).toHaveBeenCalled();
+  });
+
+  it("opens the create-dataset flow from the toolbar button", () => {
+    const spy = vi.spyOn(component, 
"onClickOpenDatasetAddComponent").mockImplementation(() => {});
+    const host = fixture.nativeElement as HTMLElement;
+    const create = host.querySelector<HTMLButtonElement>("button.create-btn");
+    expect(create, "no button matching .create-btn").not.toBeNull();
+
+    create!.click();
+
+    expect(spy).toHaveBeenCalledTimes(1);
+  });
+
+  it("hides the sort button's execution-time options on the dataset page", () 
=> {
+    // Datasets have no executions, so those sort options must not be offered.
+    const sortButton = 
fixture.debugElement.query(By.directive(SortButtonComponent)).componentInstance;
+
+    expect(sortButton.showEditTime).toBe(false);
+    expect(sortButton.showExecutionTime).toBe(false);
+  });
+});

Reply via email to