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 <[email protected]>
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 <[email protected]>
    Co-authored-by: Copilot Autofix powered by AI 
<[email protected]>
---
 .../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"]));

Reply via email to