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-7370-e4205c357a2271b89892eadb72f3efbc7012c37c in repository https://gitbox.apache.org/repos/asf/texera.git
commit 2cd542f613df3a07fccff0fbe97a9a9c012ed2a2 Author: Meng Wang <[email protected]> AuthorDate: Thu Aug 6 18:37:36 2026 -0700 test(frontend): extend UserWorkflowComponent template coverage (#7370) ### What changes were proposed in this PR? Extends `user-workflow.component.spec.ts` to render the template branches the existing class-focused tests never reached, taking `user-workflow.component.html` from ~53% to 100% (104/104; the class was already at 221/221). 11 new tests under a `template rendering` block: - **toolbar** — the Create button, the sort-button's `sortMethodChange`, and the view-type List/Card toggle. - **multi-select actions** (rendered only once an entry is checked) — Batch Select, Download-as-ZIP, Duplicate, and the Delete popconfirm. - **project actions** — the add/remove-from-project buttons that appear when a `pid` is set. - **outputs** — the rendered `texera-search-results` outputs (deleted / duplicated / refresh / notifyWorkflow) and the `#workflowCardTpl` card in card view with its card-item outputs (deleted / duplicated / refresh / checkboxChanged). Interactions go through the DOM (`By.css` + `triggerEventHandler`); the backing services are the existing stubs. No production code was changed. ### Any related issues, documentation, discussions? Closes #7362. ### How was this PR tested? `ng test --watch=false --include src/app/dashboard/component/user/user-workflow/user-workflow.component.spec.ts` — 69 passed (58 existing + 11 new), run 3x for determinism. Coverage (`--coverage`) confirms `user-workflow.component.html` at 104/104 and the class at 221/221. The failure path was verified by breaking an assertion (red, non-zero exit); eslint and prettier are clean. ### Was this PR authored or co-authored using generative AI tooling? Generated-by: Claude Code (Opus 4.8 [1M context]) --------- Signed-off-by: Xinyuan Lin <[email protected]> Co-authored-by: Xinyuan Lin <[email protected]> Co-authored-by: Copilot Autofix powered by AI <[email protected]> --- .../user-workflow/user-workflow.component.spec.ts | 144 +++++++++++++++++++++ 1 file changed, 144 insertions(+) diff --git a/frontend/src/app/dashboard/component/user/user-workflow/user-workflow.component.spec.ts b/frontend/src/app/dashboard/component/user/user-workflow/user-workflow.component.spec.ts index fa5c2b3dce..7b71ead98c 100644 --- a/frontend/src/app/dashboard/component/user/user-workflow/user-workflow.component.spec.ts +++ b/frontend/src/app/dashboard/component/user/user-workflow/user-workflow.component.spec.ts @@ -18,6 +18,7 @@ */ import { ComponentFixture, TestBed } from "@angular/core/testing"; +import { By } from "@angular/platform-browser"; import { RouterTestingModule } from "@angular/router/testing"; import { HttpClientTestingModule } from "@angular/common/http/testing"; import { FormsModule, ReactiveFormsModule } from "@angular/forms"; @@ -1019,5 +1020,148 @@ describe("SavedWorkflowSectionComponent", () => { }); }); }); + + describe("template rendering", () => { + const VIEW_MODE_KEY = "texera.userWorkflow.viewMode"; + const q = (selector: string) => fixture.debugElement.query(By.css(selector)); + + afterEach(() => { + vi.restoreAllMocks(); + localStorage.removeItem(VIEW_MODE_KEY); + }); + // The multi-select toolbar only renders when at least one entry is checked. + // Clone a real fixture entry (preserving its prototype) so the rendered + // search-results can display it, and flip `checked` on the copy so the shared + // fixture is not mutated. + const renderWithSelection = () => { + const source = testWorkflowEntries[0]; + const checkedEntry = Object.assign(Object.create(Object.getPrototypeOf(source)), source); + checkedEntry.checked = true; + component.searchResultsComponent.entries = [checkedEntry]; + fixture.detectChanges(); + }; + + it("hides the multi-select actions until an entry is checked", () => { + component.searchResultsComponent.entries = []; + fixture.detectChanges(); + expect(q('[title="Batch Select"]')).toBeNull(); + + renderWithSelection(); + expect(q('[title="Batch Select"]')).toBeTruthy(); + }); + + it("wires the Create Workflow button", () => { + const spy = vi.spyOn(component, "onClickCreateNewWorkflowFromDashboard").mockImplementation(() => {}); + q(".create-btn").triggerEventHandler("click", null); + expect(spy).toHaveBeenCalled(); + }); + + it("re-searches when the sort button changes the sort method", () => { + const searchSpy = vi.spyOn(component, "search").mockResolvedValue(undefined); + q("texera-sort-button").triggerEventHandler("sortMethodChange", "NameAsc"); + expect(component.sortMethod).toBe("NameAsc"); + expect(searchSpy).toHaveBeenCalled(); + }); + + it("wires the Batch Select button", () => { + renderWithSelection(); + const spy = vi.spyOn(component, "toggleSelection").mockImplementation(() => {}); + q('[title="Batch Select"]').triggerEventHandler("click", null); + expect(spy).toHaveBeenCalled(); + }); + + it("wires the Download-as-ZIP button", () => { + renderWithSelection(); + const spy = vi.spyOn(component, "onClickOpenDownloadZip").mockResolvedValue(undefined); + q('[title="Download added workflow as a ZIP file"]').triggerEventHandler("click", null); + expect(spy).toHaveBeenCalled(); + }); + + it("wires the Duplicate-selected button", () => { + renderWithSelection(); + const spy = vi.spyOn(component, "onClickDuplicateSelectedWorkflows").mockImplementation(() => {}); + q('[nz-tooltip="Duplicate selected workflows"]').triggerEventHandler("click", null); + expect(spy).toHaveBeenCalled(); + }); + + it("wires the Delete-selected popconfirm", () => { + renderWithSelection(); + const spy = vi.spyOn(component, "handleConfirmDeleteSelectedWorkflows").mockImplementation(() => {}); + q('[nzPopconfirmTitle="Confirm to delete selected workflows."]').triggerEventHandler("nzOnConfirm", null); + expect(spy).toHaveBeenCalled(); + }); + + it("shows and wires the project add/remove buttons when a pid is set", () => { + component.pid = 1; + fixture.detectChanges(); + const addSpy = vi.spyOn(component, "onClickOpenAddWorkflow").mockImplementation(() => {}); + const removeSpy = vi.spyOn(component, "onClickOpenRemoveWorkflow").mockImplementation(() => {}); + + q('[title="Add workflow(s) to project"]').triggerEventHandler("click", null); + q('[title="Remove workflow(s) from project"]').triggerEventHandler("click", null); + + expect(addSpy).toHaveBeenCalled(); + expect(removeSpy).toHaveBeenCalled(); + }); + + it("switches the view type through the List/Card buttons", () => { + component.viewType = "card"; + fixture.detectChanges(); + + q('[title="List View"]').triggerEventHandler("click", null); + expect(component.viewType).toBe("list"); + + fixture.detectChanges(); + q('[title="Card View"]').triggerEventHandler("click", null); + expect(component.viewType).toBe("card"); + }); + + it("wires the rendered search-results outputs to the component", () => { + const results = fixture.debugElement.query(By.directive(SearchResultsComponent)); + const deleteSpy = vi.spyOn(component, "deleteWorkflow").mockImplementation(() => {}); + const duplicateSpy = vi.spyOn(component, "onClickDuplicateWorkflow").mockResolvedValue(undefined); + const refreshSpy = vi.spyOn(component, "refreshSearchResult").mockImplementation(() => {}); + const tooltipSpy = vi.spyOn(component, "updateTooltip").mockImplementation(() => {}); + const entry = testWorkflowEntries[0]; + + results.triggerEventHandler("deleted", entry); + results.triggerEventHandler("duplicated", entry); + results.triggerEventHandler("refresh", undefined); + results.triggerEventHandler("notifyWorkflow", undefined); + + expect(deleteSpy).toHaveBeenCalledWith(entry); + expect(duplicateSpy).toHaveBeenCalledWith(entry); + expect(refreshSpy).toHaveBeenCalled(); + expect(tooltipSpy).toHaveBeenCalled(); + }); + + it("renders the workflow card template in card view and wires its outputs", () => { + const source = testWorkflowEntries[0]; + const entry = Object.assign(Object.create(Object.getPrototypeOf(source)), source); + component.viewType = "card"; + component.searchResultsComponent.entries = [entry]; + fixture.detectChanges(); + + const card = fixture.debugElement.query(By.css("texera-card-item")); + expect(card).toBeTruthy(); + + const deleteSpy = vi.spyOn(component, "deleteWorkflow").mockImplementation(() => {}); + const duplicateSpy = vi.spyOn(component, "onClickDuplicateWorkflow").mockResolvedValue(undefined); + const refreshSpy = vi.spyOn(component, "refreshSearchResult").mockImplementation(() => {}); + const checkboxSpy = vi + .spyOn(component.searchResultsComponent, "onEntryCheckboxChange") + .mockImplementation(() => {}); + + card.triggerEventHandler("deleted", undefined); + card.triggerEventHandler("duplicated", undefined); + card.triggerEventHandler("refresh", undefined); + card.triggerEventHandler("checkboxChanged", undefined); + + expect(deleteSpy).toHaveBeenCalledWith(entry); + expect(duplicateSpy).toHaveBeenCalledWith(entry); + expect(refreshSpy).toHaveBeenCalled(); + expect(checkboxSpy).toHaveBeenCalled(); + }); + }); }); });
