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-6760-b1c1e025377deaf8150427ae8f605990dab1922e in repository https://gitbox.apache.org/repos/asf/texera.git
commit 8a411431225e87f04a367f41a5c65bbbdab984d3 Author: Meng Wang <[email protected]> AuthorDate: Tue Jul 21 22:43:23 2026 -0700 test(frontend): cover ListItemComponent edit helpers and FilesUploaderComponent conflict prompts (#6760) ### What changes were proposed in this PR? Extends two dashboard component specs to cover their remaining untested edit/interaction methods (Vitest; no production code changed). **`ListItemComponent`** (+5 tests) — `setEditingState` (toggles the name / description edit flags), `renderMarkdownPreview` (strips markdown syntax and collapses whitespace), and `updateProperty` (missing-id guard, success persistence + exit-edit-mode, and error revert + notify). > Note: the other ListItem methods the issue lists (`onEditName` / > `onEditDescription` / `toggleLike` / `onCheckboxChange` / `openDetailModal` / > `initializeEntry` / `confirmUpdate*`) are already covered on `main` (added in > #6557), so this PR only fills the remaining gaps. **`FilesUploaderComponent`** (+4 tests) — `markForceRestart` (flags the item), `getOwnerAndName` (returns the owner/name inputs), and `askResumeOrSkip` / `askUploadOrSkip` (resolve with the clicked conflict-modal footer action). The modal is driven through the spec's existing capturing double. ### Any related issues, documentation, discussions? Closes #6749 ### How was this PR tested? Extended unit tests, run locally in `frontend/` (all green; each spec's failure path was verified by breaking an assertion to confirm it goes red): ``` ng test --watch=false --include src/app/dashboard/component/user/list-item/list-item.component.spec.ts # 25 passed ng test --watch=false --include src/app/dashboard/component/user/files-uploader/files-uploader.component.spec.ts # 12 passed prettier --write <specs> # clean eslint <specs> # clean ``` ### Was this PR authored or co-authored using generative AI tooling? Generated-by: Claude Code (Opus 4.8 [1M context]) --- .../files-uploader.component.spec.ts | 39 ++++++++++++ .../user/list-item/list-item.component.spec.ts | 70 ++++++++++++++++++++++ 2 files changed, 109 insertions(+) diff --git a/frontend/src/app/dashboard/component/user/files-uploader/files-uploader.component.spec.ts b/frontend/src/app/dashboard/component/user/files-uploader/files-uploader.component.spec.ts index 43398d7efa..88f04e2efc 100644 --- a/frontend/src/app/dashboard/component/user/files-uploader/files-uploader.component.spec.ts +++ b/frontend/src/app/dashboard/component/user/files-uploader/files-uploader.component.spec.ts @@ -219,4 +219,43 @@ describe("FilesUploaderComponent", () => { expect(component.fileUploadBannerType).toBe("warning"); expect(component.fileUploadBannerMessage).toBe("heads up"); }); + + describe("force-restart, owner/name, and conflict prompts", () => { + type Item = { name: string; file: { size: number }; restart?: boolean }; + const asComp = () => + component as unknown as { + markForceRestart: (i: Item) => void; + getOwnerAndName: () => { ownerEmail: string; datasetName: string }; + askResumeOrSkip: (i: Item, showForAll: boolean) => Promise<string>; + askUploadOrSkip: (i: Item, showForAll: boolean) => Promise<string>; + }; + const lastFooter = () => + (modals[modals.length - 1] as unknown as { nzFooter: Array<{ label: string; onClick: () => void }> }).nzFooter; + + it("markForceRestart flags the item for a forced restart", () => { + const item: Item = { name: "f.csv", file: { size: 1 }, restart: false }; + asComp().markForceRestart(item); + expect(item.restart).toBe(true); + }); + + it("getOwnerAndName returns the owner email and dataset name inputs", () => { + expect(asComp().getOwnerAndName()).toEqual({ ownerEmail: "[email protected]", datasetName: "dataset" }); + }); + + it("askResumeOrSkip resolves with the clicked footer action", async () => { + const promise = asComp().askResumeOrSkip({ name: "owner/data/f.csv", file: { size: 100 } }, true); + lastFooter() + .find(b => b.label === "Resume")! + .onClick(); + await expect(promise).resolves.toBe("resume"); + }); + + it("askUploadOrSkip resolves with the clicked footer action", async () => { + const promise = asComp().askUploadOrSkip({ name: "owner/data/f.csv", file: { size: 100 } }, false); + lastFooter() + .find(b => b.label === "Skip")! + .onClick(); + await expect(promise).resolves.toBe("skip"); + }); + }); }); diff --git a/frontend/src/app/dashboard/component/user/list-item/list-item.component.spec.ts b/frontend/src/app/dashboard/component/user/list-item/list-item.component.spec.ts index eca823a4ee..3e7bc25c0c 100644 --- a/frontend/src/app/dashboard/component/user/list-item/list-item.component.spec.ts +++ b/frontend/src/app/dashboard/component/user/list-item/list-item.component.spec.ts @@ -350,4 +350,74 @@ describe("ListItemComponent", () => { expect(component.viewCount).toBe(5); // 4 + 1 }); }); + + describe("editing state, markdown preview, and updateProperty", () => { + const asComp = () => + component as unknown as { + setEditingState: (p: "name" | "description", s: boolean) => void; + renderMarkdownPreview: (t: string | undefined) => void; + updateProperty: ( + m: (id: number, v: string) => unknown, + p: "name" | "description", + v: string, + o: string | undefined + ) => void; + }; + + it("setEditingState toggles the name / description edit flags", () => { + asComp().setEditingState("name", true); + expect(component.editingName).toBe(true); + asComp().setEditingState("description", true); + expect(component.editingDescription).toBe(true); + asComp().setEditingState("name", false); + expect(component.editingName).toBe(false); + }); + + it("renderMarkdownPreview strips markdown syntax and collapses whitespace", () => { + asComp().renderMarkdownPreview("# **Hello** [link](http://x)"); + expect(component.renderedDescription).toBe("Hello link"); + asComp().renderMarkdownPreview(" "); + expect(component.renderedDescription).toBe(""); + asComp().renderMarkdownPreview(undefined); + expect(component.renderedDescription).toBe(""); + }); + + it("updateProperty reports a missing id and skips the update", () => { + const errorSpy = vi.spyOn(TestBed.inject(NotificationService), "error"); + component.entry = { name: "x", type: "workflow" } as unknown as DashboardEntry; + const updateMethod = vi.fn(); + + asComp().updateProperty(updateMethod, "name", "new", "old"); + + expect(errorSpy).toHaveBeenCalledWith("Id is missing"); + expect(updateMethod).not.toHaveBeenCalled(); + }); + + it("updateProperty persists the new value and exits edit mode on success", () => { + component.entry = { id: 1, name: "old", type: "workflow" } as unknown as DashboardEntry; + component.editingName = true; + const updateMethod = vi.fn(() => of({})); + + asComp().updateProperty(updateMethod, "name", "new-name", "old"); + + expect(updateMethod).toHaveBeenCalledWith(1, "new-name"); + expect(component.entry.name).toBe("new-name"); + expect(component.editingName).toBe(false); + }); + + it("updateProperty reverts to the original value and exits edit mode on error", () => { + const errorSpy = vi.spyOn(TestBed.inject(NotificationService), "error"); + // The name field is two-way bound, so entry.name already holds the edited + // value by the time updateProperty runs; the error handler must roll it back. + component.entry = { id: 1, name: "new-name", type: "workflow" } as unknown as DashboardEntry; + component.editingName = true; + const updateMethod = vi.fn(() => throwError(() => new Error("boom"))); + + asComp().updateProperty(updateMethod, "name", "new-name", "old"); + + expect(component.entry.name).toBe("old"); + expect(errorSpy).toHaveBeenCalled(); + expect(component.editingName).toBe(false); + }); + }); });
