You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@texera.apache.org by gi...@apache.org on 2026/08/06 05:45:16 UTC
(texera) branch gh-readonly-queue/main/pr-7322-69c66a17647f3e530a90486d7acde5b0120cda74 created (now b75e821382)
This is an automated email from the ASF dual-hosted git repository.
github-merge-queue[bot] pushed a change to branch gh-readonly-queue/main/pr-7322-69c66a17647f3e530a90486d7acde5b0120cda74
in repository https://gitbox.apache.org/repos/asf/texera.git
at b75e821382 test(frontend): cover the files-uploader restart branches (#7322)
This branch includes the following new commits:
new b75e821382 test(frontend): cover the files-uploader restart branches (#7322)
The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails. The revisions
listed as "add" were already present in the repository and have only
been added to this reference.
(texera) 01/01: test(frontend): cover the files-uploader restart branches (#7322)
Posted by gi...@apache.org.
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-7322-69c66a17647f3e530a90486d7acde5b0120cda74
in repository https://gitbox.apache.org/repos/asf/texera.git
commit b75e82138278daac68dcec6ab6adfb18179c0f45
Author: Xinyuan Lin <xi...@uci.edu>
AuthorDate: Wed Aug 5 22:44:55 2026 -0700
test(frontend): cover the files-uploader restart branches (#7322)
### What changes were proposed in this PR?
The files-uploader's conflict dialog offers **Resume** and **Restart**,
and the existing spec only ever took the Resume half — leaving
`Restart`, `Restart For All` and `markForceRestart` untested.
The two choices differ by exactly one observable:
```
Resume -> item.restart stays unset -> uploader continues the existing multipart session
Restart -> item.restart = true -> uploader calls the backend with type=forceRestart
```
So a Resume/Restart mix-up silently resumes a session the user
explicitly asked to discard, and nothing fails.
Five tests:
| Case | What it pins |
|---|---|
| Restart sets the flag | the single-file restart branch |
| Resume leaves it unset | written as the pair, so a `markForceRestart`
wrongly added to Resume is caught too |
| `Restart For All` | the flag reaches files that **never prompted** —
the latch applies the restart, it does not merely suppress the dialog |
| `Resume For All` | must **not** set the flag, distinguishing the two
latches |
| non-conflicting file | passes straight through, no dialog at all |
Both restart assertions were checked by mutation: dropping
`markForceRestart` from the corresponding branch turns each red.
Reverted afterwards, and the component diff is empty.
These are additions to the spec's existing harness — it already captures
modal configs and clicks their footer buttons by label — not new
infrastructure.
No production file is touched.
### Any related issues, documentation, discussions?
Closes #7320
### How was this PR tested?
```
npx ng test --watch=false --include="**/files-uploader.component.spec.ts"
```
```
✓ src/app/dashboard/component/user/files-uploader/files-uploader.component.spec.ts (13 tests)
Test Files 1 passed (1)
```
`yarn format:ci` passes (prettier-eslint + eslint), which Vitest does
not cover on its own.
### Was this PR authored or co-authored using generative AI tooling?
Generated-by: Claude Code (Opus 5)
---------
Signed-off-by: Xinyuan Lin <xi...@uci.edu>
Co-authored-by: Copilot Autofix powered by AI <17...@users.noreply.github.com>
---
.../files-uploader.component.spec.ts | 91 ++++++++++++++++++++++
1 file changed, 91 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..1882de68f8 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
@@ -148,6 +148,97 @@ describe("FilesUploaderComponent", () => {
expect((await emitted).map(item => item.name)).toEqual(["same.csv"]);
});
+ /**
+ * The Restart choices are the half of the conflict dialog the existing tests never take. They
+ * differ from Resume by exactly one observable: `item.restart`, which flips from its default
+ * `false` to `true` and makes the uploader call the backend with type=forceRestart instead of
+ * continuing the existing multipart session. A Resume/Restart mix-up therefore silently resumes a
+ * session the user asked to discard.
+ */
+ it("marks a file for force-restart when Restart is chosen", async () => {
+ datasetService.findExistingUploadFiles.mockReturnValue(of([]));
+ const emitted = new Promise<FileUploadItem[]>(resolve => component.uploadedFiles.subscribe(resolve));
+
+ component.fileDropped([droppedFile("failed.csv", new File(["half"], "failed.csv"))]);
+
+ await waitUntil(() => modals.length === 1);
+ modals[0].nzFooter.find(button => button.label === "Restart")?.onClick();
+
+ const items = await emitted;
+ expect(items.map(item => item.name)).toEqual(["failed.csv"]);
+ expect(items[0].restart).toBe(true);
+ });
+
+ it("leaves the restart flag unset when Resume is chosen", async () => {
+ // The counterpart of the test above: same file, other button. Without this pair, a
+ // markForceRestart call added to the Resume branch would go unnoticed.
+ datasetService.findExistingUploadFiles.mockReturnValue(of([]));
+ const emitted = new Promise<FileUploadItem[]>(resolve => component.uploadedFiles.subscribe(resolve));
+
+ component.fileDropped([droppedFile("failed.csv", new File(["half"], "failed.csv"))]);
+
+ await waitUntil(() => modals.length === 1);
+ modals[0].nzFooter.find(button => button.label === "Resume")?.onClick();
+
+ const items = await emitted;
+ expect(items.map(item => item.name)).toEqual(["failed.csv"]);
+ expect(items[0].restart).toBeFalsy();
+ });
+
+ it("restarts every remaining conflicting file after one Restart For All choice", async () => {
+ datasetService.listMultipartUploads.mockReturnValue(of(["one.csv", "two.csv"]));
+ datasetService.findExistingUploadFiles.mockReturnValue(of([]));
+ const emitted = new Promise<FileUploadItem[]>(resolve => component.uploadedFiles.subscribe(resolve));
+
+ component.fileDropped([
+ droppedFile("one.csv", new File(["one"], "one.csv")),
+ droppedFile("two.csv", new File(["two"], "two.csv")),
+ ]);
+
+ await waitUntil(() => modals.length === 1);
+ modals[0].nzFooter.find(button => button.label === "Restart For All")?.onClick();
+
+ const items = await emitted;
+ expect(items.map(item => item.name)).toEqual(["one.csv", "two.csv"]);
+ // Both files carry the flag, and the second one never prompted - the "For All" latch has to
+ // apply the restart itself rather than just suppressing the dialog.
+ expect(items.map(item => item.restart)).toEqual([true, true]);
+ expect(modals).toHaveLength(1);
+ });
+
+ it("resumes every remaining conflicting file after one Resume For All choice", async () => {
+ datasetService.listMultipartUploads.mockReturnValue(of(["one.csv", "two.csv"]));
+ datasetService.findExistingUploadFiles.mockReturnValue(of([]));
+ const emitted = new Promise<FileUploadItem[]>(resolve => component.uploadedFiles.subscribe(resolve));
+
+ component.fileDropped([
+ droppedFile("one.csv", new File(["one"], "one.csv")),
+ droppedFile("two.csv", new File(["two"], "two.csv")),
+ ]);
+
+ await waitUntil(() => modals.length === 1);
+ modals[0].nzFooter.find(button => button.label === "Resume For All")?.onClick();
+
+ const items = await emitted;
+ expect(items.map(item => item.name)).toEqual(["one.csv", "two.csv"]);
+ // Distinguishes the two latches: resumeAll must NOT set the flag restartAll sets.
+ expect(items.every(item => !item.restart)).toBe(true);
+ expect(modals).toHaveLength(1);
+ });
+
+ it("passes a non-conflicting file straight through without prompting", async () => {
+ datasetService.listMultipartUploads.mockReturnValue(of(["other.csv"]));
+ datasetService.findExistingUploadFiles.mockReturnValue(of([]));
+ const emitted = new Promise<FileUploadItem[]>(resolve => component.uploadedFiles.subscribe(resolve));
+
+ component.fileDropped([droppedFile("clean.csv", new File(["clean"], "clean.csv"))]);
+
+ const items = await emitted;
+ expect(items.map(item => item.name)).toEqual(["clean.csv"]);
+ expect(items[0].restart).toBeFalsy();
+ expect(modals).toHaveLength(0);
+ });
+
it("skips all matching files after one Skip For All choice", async () => {
datasetService.listMultipartUploads.mockReturnValue(of([]));
datasetService.findExistingUploadFiles.mockReturnValue(of(["one.csv", "two.csv"]));