mengw15 commented on code in PR #6527:
URL: https://github.com/apache/texera/pull/6527#discussion_r3609564347


##########
frontend/src/app/dashboard/component/admin/settings/admin-settings.component.spec.ts:
##########
@@ -123,4 +124,277 @@ describe("AdminSettingsComponent", () => {
     httpTestingController.expectNone((req: { method: string }) => req.method 
=== "PUT");
     expect(errorSpy).toHaveBeenCalledWith("Settings have not loaded; refresh 
before saving.");
   });
+
+  describe("save / reset handlers", () => {
+    let msgSuccess: ReturnType<typeof vi.fn>;
+    let msgError: ReturnType<typeof vi.fn>;
+    let msgInfo: ReturnType<typeof vi.fn>;
+    let notifySuccess: ReturnType<typeof vi.fn>;
+    let notifyError: ReturnType<typeof vi.fn>;
+    let notifyInfo: ReturnType<typeof vi.fn>;
+
+    const updateUrl = (key: string) => `${SETTINGS_URL}/${key}`;
+    const resetUrl = (key: string) => `${SETTINGS_URL}/reset/${key}`;
+    const HTTP_ERROR = { status: 500, statusText: "Server Error" };
+
+    // Flush the ngOnInit bulk GET so the component is `settingsLoaded`.
+    function completeLoad(settings: Record<string, string> = {}): void {
+      httpTestingController.expectOne(SETTINGS_URL).flush(settings);
+    }
+
+    beforeEach(() => {
+      // The reset/save handlers schedule window.location.reload() via 
setTimeout;
+      // fake timers keep it from firing (jsdom can't navigate).
+      vi.useFakeTimers();
+
+      const message = TestBed.inject(NzMessageService);
+      const notification = TestBed.inject(NotificationService);
+      msgSuccess = vi.spyOn(message, "success").mockReturnValue({} as 
ReturnType<NzMessageService["success"]>);
+      msgError = vi.spyOn(message, "error").mockReturnValue({} as 
ReturnType<NzMessageService["error"]>);
+      msgInfo = vi.spyOn(message, "info").mockReturnValue({} as 
ReturnType<NzMessageService["info"]>);
+      notifySuccess = vi.spyOn(notification, "success").mockImplementation(() 
=> {});
+      notifyError = vi.spyOn(notification, "error").mockImplementation(() => 
{});
+      notifyInfo = vi.spyOn(notification, "info").mockImplementation(() => {});
+    });
+
+    afterEach(() => {
+      vi.useRealTimers();
+      vi.restoreAllMocks();
+    });

Review Comment:
   Added `httpTestingController.verify()` to the nested `afterEach`. It first 
drains nz-icon's lazily-fetched `assets/*.svg` requests so verify() asserts 
only on the requests the handlers issue — which now catches leaked forkJoin 
requests (confirmed by mutating a test to under-flush).



##########
frontend/src/app/dashboard/component/admin/settings/admin-settings.component.spec.ts:
##########
@@ -123,4 +124,277 @@ describe("AdminSettingsComponent", () => {
     httpTestingController.expectNone((req: { method: string }) => req.method 
=== "PUT");
     expect(errorSpy).toHaveBeenCalledWith("Settings have not loaded; refresh 
before saving.");
   });
+
+  describe("save / reset handlers", () => {
+    let msgSuccess: ReturnType<typeof vi.fn>;
+    let msgError: ReturnType<typeof vi.fn>;
+    let msgInfo: ReturnType<typeof vi.fn>;
+    let notifySuccess: ReturnType<typeof vi.fn>;
+    let notifyError: ReturnType<typeof vi.fn>;
+    let notifyInfo: ReturnType<typeof vi.fn>;
+
+    const updateUrl = (key: string) => `${SETTINGS_URL}/${key}`;
+    const resetUrl = (key: string) => `${SETTINGS_URL}/reset/${key}`;
+    const HTTP_ERROR = { status: 500, statusText: "Server Error" };
+
+    // Flush the ngOnInit bulk GET so the component is `settingsLoaded`.
+    function completeLoad(settings: Record<string, string> = {}): void {
+      httpTestingController.expectOne(SETTINGS_URL).flush(settings);
+    }
+
+    beforeEach(() => {
+      // The reset/save handlers schedule window.location.reload() via 
setTimeout;
+      // fake timers keep it from firing (jsdom can't navigate).
+      vi.useFakeTimers();
+
+      const message = TestBed.inject(NzMessageService);
+      const notification = TestBed.inject(NotificationService);
+      msgSuccess = vi.spyOn(message, "success").mockReturnValue({} as 
ReturnType<NzMessageService["success"]>);
+      msgError = vi.spyOn(message, "error").mockReturnValue({} as 
ReturnType<NzMessageService["error"]>);
+      msgInfo = vi.spyOn(message, "info").mockReturnValue({} as 
ReturnType<NzMessageService["info"]>);
+      notifySuccess = vi.spyOn(notification, "success").mockImplementation(() 
=> {});
+      notifyError = vi.spyOn(notification, "error").mockImplementation(() => 
{});
+      notifyInfo = vi.spyOn(notification, "info").mockImplementation(() => {});
+    });
+
+    afterEach(() => {
+      vi.useRealTimers();
+      vi.restoreAllMocks();
+    });
+
+    describe("branding", () => {
+      it("saveLogos PUTs only the branding assets that are set and notifies 
success", () => {
+        completeLoad();
+        component.logoData = "logo.png";
+        component.miniLogoData = "mini.png";
+        component.faviconData = null;
+
+        component.saveLogos();
+
+        const logoReq = httpTestingController.expectOne(updateUrl("logo"));
+        expect(logoReq.request.method).toBe("PUT");
+        expect(logoReq.request.body).toEqual({ value: "logo.png" });
+        const miniReq = 
httpTestingController.expectOne(updateUrl("mini_logo"));
+        expect(miniReq.request.body).toEqual({ value: "mini.png" });
+        httpTestingController.expectNone(updateUrl("favicon"));
+        logoReq.flush(null);
+        miniReq.flush(null);
+
+        expect(msgSuccess).toHaveBeenCalledWith("Branding saved 
successfully.");
+      });
+
+      it("saveLogos does nothing when no branding asset is set", () => {
+        completeLoad();
+        component.logoData = null;
+        component.miniLogoData = null;
+        component.faviconData = null;
+
+        component.saveLogos();
+
+        httpTestingController.expectNone((req: { method: string }) => 
req.method === "PUT");
+        expect(msgSuccess).not.toHaveBeenCalled();
+      });
+
+      it("saveLogos notifies an error when the request fails", () => {
+        completeLoad();
+        component.logoData = "logo.png";
+        component.miniLogoData = null;
+        component.faviconData = null;
+
+        component.saveLogos();
+
+        httpTestingController.expectOne(updateUrl("logo")).flush("boom", 
HTTP_ERROR);
+        expect(msgError).toHaveBeenCalledWith("Failed to save branding.");
+      });
+
+      it("resetBranding POSTs a reset for all three branding settings", () => {
+        completeLoad();
+
+        component.resetBranding();
+
+        ["logo", "mini_logo", "favicon"].forEach(key => {
+          const req = httpTestingController.expectOne(resetUrl(key));
+          expect(req.request.method).toBe("POST");
+          req.flush(null);
+        });
+        expect(msgInfo).toHaveBeenCalledWith("Resetting branding...");
+      });
+    });
+
+    describe("tabs", () => {
+      it("saveTabs PUTs every sidebar tab and notifies success", () => {
+        completeLoad();
+
+        component.saveTabs();
+
+        Object.keys(component.sidebarTabs).forEach(tab => {
+          const req = httpTestingController.expectOne(updateUrl(tab));
+          expect(req.request.method).toBe("PUT");
+          req.flush(null);
+        });
+        expect(msgSuccess).toHaveBeenCalledWith("Tabs saved successfully.");
+      });
+
+      it("saveTabs notifies an error when a request fails", () => {
+        completeLoad();
+
+        component.saveTabs();
+
+        const firstTab = Object.keys(component.sidebarTabs)[0];
+        httpTestingController.expectOne(updateUrl(firstTab)).flush("boom", 
HTTP_ERROR);
+        expect(msgError).toHaveBeenCalledWith("Failed to save tabs.");

Review Comment:
   Fixed — the test now flushes all 12 tab PUTs (the last one as the error), so 
forkJoin errors with nothing left pending. verify() in afterEach now confirms 
it.



##########
frontend/src/app/dashboard/component/admin/settings/admin-settings.component.spec.ts:
##########
@@ -123,4 +124,277 @@ describe("AdminSettingsComponent", () => {
     httpTestingController.expectNone((req: { method: string }) => req.method 
=== "PUT");
     expect(errorSpy).toHaveBeenCalledWith("Settings have not loaded; refresh 
before saving.");
   });
+
+  describe("save / reset handlers", () => {
+    let msgSuccess: ReturnType<typeof vi.fn>;
+    let msgError: ReturnType<typeof vi.fn>;
+    let msgInfo: ReturnType<typeof vi.fn>;
+    let notifySuccess: ReturnType<typeof vi.fn>;
+    let notifyError: ReturnType<typeof vi.fn>;
+    let notifyInfo: ReturnType<typeof vi.fn>;
+
+    const updateUrl = (key: string) => `${SETTINGS_URL}/${key}`;
+    const resetUrl = (key: string) => `${SETTINGS_URL}/reset/${key}`;
+    const HTTP_ERROR = { status: 500, statusText: "Server Error" };
+
+    // Flush the ngOnInit bulk GET so the component is `settingsLoaded`.
+    function completeLoad(settings: Record<string, string> = {}): void {
+      httpTestingController.expectOne(SETTINGS_URL).flush(settings);
+    }
+
+    beforeEach(() => {
+      // The reset/save handlers schedule window.location.reload() via 
setTimeout;
+      // fake timers keep it from firing (jsdom can't navigate).
+      vi.useFakeTimers();
+
+      const message = TestBed.inject(NzMessageService);
+      const notification = TestBed.inject(NotificationService);
+      msgSuccess = vi.spyOn(message, "success").mockReturnValue({} as 
ReturnType<NzMessageService["success"]>);
+      msgError = vi.spyOn(message, "error").mockReturnValue({} as 
ReturnType<NzMessageService["error"]>);
+      msgInfo = vi.spyOn(message, "info").mockReturnValue({} as 
ReturnType<NzMessageService["info"]>);
+      notifySuccess = vi.spyOn(notification, "success").mockImplementation(() 
=> {});
+      notifyError = vi.spyOn(notification, "error").mockImplementation(() => 
{});
+      notifyInfo = vi.spyOn(notification, "info").mockImplementation(() => {});
+    });
+
+    afterEach(() => {
+      vi.useRealTimers();
+      vi.restoreAllMocks();
+    });
+
+    describe("branding", () => {
+      it("saveLogos PUTs only the branding assets that are set and notifies 
success", () => {
+        completeLoad();
+        component.logoData = "logo.png";
+        component.miniLogoData = "mini.png";
+        component.faviconData = null;
+
+        component.saveLogos();
+
+        const logoReq = httpTestingController.expectOne(updateUrl("logo"));
+        expect(logoReq.request.method).toBe("PUT");
+        expect(logoReq.request.body).toEqual({ value: "logo.png" });
+        const miniReq = 
httpTestingController.expectOne(updateUrl("mini_logo"));
+        expect(miniReq.request.body).toEqual({ value: "mini.png" });
+        httpTestingController.expectNone(updateUrl("favicon"));
+        logoReq.flush(null);
+        miniReq.flush(null);
+
+        expect(msgSuccess).toHaveBeenCalledWith("Branding saved 
successfully.");
+      });
+
+      it("saveLogos does nothing when no branding asset is set", () => {
+        completeLoad();
+        component.logoData = null;
+        component.miniLogoData = null;
+        component.faviconData = null;
+
+        component.saveLogos();
+
+        httpTestingController.expectNone((req: { method: string }) => 
req.method === "PUT");
+        expect(msgSuccess).not.toHaveBeenCalled();
+      });
+
+      it("saveLogos notifies an error when the request fails", () => {
+        completeLoad();
+        component.logoData = "logo.png";
+        component.miniLogoData = null;
+        component.faviconData = null;
+
+        component.saveLogos();
+
+        httpTestingController.expectOne(updateUrl("logo")).flush("boom", 
HTTP_ERROR);
+        expect(msgError).toHaveBeenCalledWith("Failed to save branding.");
+      });
+
+      it("resetBranding POSTs a reset for all three branding settings", () => {
+        completeLoad();
+
+        component.resetBranding();
+
+        ["logo", "mini_logo", "favicon"].forEach(key => {
+          const req = httpTestingController.expectOne(resetUrl(key));
+          expect(req.request.method).toBe("POST");
+          req.flush(null);
+        });
+        expect(msgInfo).toHaveBeenCalledWith("Resetting branding...");
+      });
+    });
+
+    describe("tabs", () => {
+      it("saveTabs PUTs every sidebar tab and notifies success", () => {
+        completeLoad();
+
+        component.saveTabs();
+
+        Object.keys(component.sidebarTabs).forEach(tab => {
+          const req = httpTestingController.expectOne(updateUrl(tab));
+          expect(req.request.method).toBe("PUT");
+          req.flush(null);
+        });
+        expect(msgSuccess).toHaveBeenCalledWith("Tabs saved successfully.");
+      });
+
+      it("saveTabs notifies an error when a request fails", () => {
+        completeLoad();
+
+        component.saveTabs();
+
+        const firstTab = Object.keys(component.sidebarTabs)[0];
+        httpTestingController.expectOne(updateUrl(firstTab)).flush("boom", 
HTTP_ERROR);
+        expect(msgError).toHaveBeenCalledWith("Failed to save tabs.");
+      });
+
+      it("resetTabs POSTs a reset for every sidebar tab", () => {
+        completeLoad();
+
+        component.resetTabs();
+
+        Object.keys(component.sidebarTabs).forEach(tab => {
+          httpTestingController.expectOne(resetUrl(tab)).flush(null);
+        });
+        expect(msgInfo).toHaveBeenCalledWith("Resetting tabs...");
+      });
+    });
+
+    describe("dataset settings", () => {
+      it("saveDatasetSettings PUTs the four upload settings and notifies 
success", () => {
+        completeLoad(); // defaults (20 / 3 / 10 / 50) are valid
+
+        component.saveDatasetSettings();
+
+        const expectPut = (key: string, value: string) => {
+          const req = httpTestingController.expectOne(updateUrl(key));
+          expect(req.request.method).toBe("PUT");
+          expect(req.request.body).toEqual({ value });
+          req.flush(null);
+        };
+        expectPut("max_number_of_concurrent_uploading_file", "3");
+        expectPut("single_file_upload_max_size_mib", "20");
+        expectPut("max_number_of_concurrent_uploading_file_chunks", "10");
+        expectPut("multipart_upload_chunk_size_mib", "50");
+
+        expect(msgSuccess).toHaveBeenCalledWith("Dataset upload settings saved 
successfully.");
+      });
+
+      it("saveDatasetSettings rejects non-positive values without saving", () 
=> {
+        completeLoad();
+        component.maxFileSizeMiB = 0;
+
+        component.saveDatasetSettings();
+
+        httpTestingController.expectNone((req: { method: string }) => 
req.method === "PUT");
+        expect(msgError).toHaveBeenCalledWith("Please enter valid integer 
values.");
+      });
+
+      it("saveDatasetSettings rejects a configuration that would exceed the 
10,000-part limit", () => {
+        completeLoad();
+        component.maxFileSizeMiB = 100000;
+        component.chunkSizeMiB = 1;
+
+        component.saveDatasetSettings();
+
+        httpTestingController.expectNone((req: { method: string }) => 
req.method === "PUT");
+        expect(msgError).toHaveBeenCalled();
+      });
+
+      it("saveDatasetSettings notifies an error when a request fails", () => {
+        completeLoad();
+
+        component.saveDatasetSettings();
+
+        
httpTestingController.expectOne(updateUrl("max_number_of_concurrent_uploading_file")).flush("boom",
 HTTP_ERROR);
+        expect(msgError).toHaveBeenCalledWith("Failed to save dataset 
settings.");

Review Comment:
   Fixed — the test now flushes all 4 PUTs (the last as the error), so no 
requests remain pending.



##########
frontend/src/app/dashboard/component/admin/settings/admin-settings.component.spec.ts:
##########
@@ -123,4 +124,277 @@ describe("AdminSettingsComponent", () => {
     httpTestingController.expectNone((req: { method: string }) => req.method 
=== "PUT");
     expect(errorSpy).toHaveBeenCalledWith("Settings have not loaded; refresh 
before saving.");
   });
+
+  describe("save / reset handlers", () => {
+    let msgSuccess: ReturnType<typeof vi.fn>;
+    let msgError: ReturnType<typeof vi.fn>;
+    let msgInfo: ReturnType<typeof vi.fn>;
+    let notifySuccess: ReturnType<typeof vi.fn>;
+    let notifyError: ReturnType<typeof vi.fn>;
+    let notifyInfo: ReturnType<typeof vi.fn>;
+
+    const updateUrl = (key: string) => `${SETTINGS_URL}/${key}`;
+    const resetUrl = (key: string) => `${SETTINGS_URL}/reset/${key}`;
+    const HTTP_ERROR = { status: 500, statusText: "Server Error" };
+
+    // Flush the ngOnInit bulk GET so the component is `settingsLoaded`.
+    function completeLoad(settings: Record<string, string> = {}): void {
+      httpTestingController.expectOne(SETTINGS_URL).flush(settings);
+    }
+
+    beforeEach(() => {
+      // The reset/save handlers schedule window.location.reload() via 
setTimeout;
+      // fake timers keep it from firing (jsdom can't navigate).
+      vi.useFakeTimers();
+
+      const message = TestBed.inject(NzMessageService);
+      const notification = TestBed.inject(NotificationService);
+      msgSuccess = vi.spyOn(message, "success").mockReturnValue({} as 
ReturnType<NzMessageService["success"]>);
+      msgError = vi.spyOn(message, "error").mockReturnValue({} as 
ReturnType<NzMessageService["error"]>);
+      msgInfo = vi.spyOn(message, "info").mockReturnValue({} as 
ReturnType<NzMessageService["info"]>);
+      notifySuccess = vi.spyOn(notification, "success").mockImplementation(() 
=> {});
+      notifyError = vi.spyOn(notification, "error").mockImplementation(() => 
{});
+      notifyInfo = vi.spyOn(notification, "info").mockImplementation(() => {});
+    });
+
+    afterEach(() => {
+      vi.useRealTimers();
+      vi.restoreAllMocks();
+    });
+
+    describe("branding", () => {
+      it("saveLogos PUTs only the branding assets that are set and notifies 
success", () => {
+        completeLoad();
+        component.logoData = "logo.png";
+        component.miniLogoData = "mini.png";
+        component.faviconData = null;
+
+        component.saveLogos();
+
+        const logoReq = httpTestingController.expectOne(updateUrl("logo"));
+        expect(logoReq.request.method).toBe("PUT");
+        expect(logoReq.request.body).toEqual({ value: "logo.png" });
+        const miniReq = 
httpTestingController.expectOne(updateUrl("mini_logo"));
+        expect(miniReq.request.body).toEqual({ value: "mini.png" });
+        httpTestingController.expectNone(updateUrl("favicon"));
+        logoReq.flush(null);
+        miniReq.flush(null);
+
+        expect(msgSuccess).toHaveBeenCalledWith("Branding saved 
successfully.");
+      });
+
+      it("saveLogos does nothing when no branding asset is set", () => {
+        completeLoad();
+        component.logoData = null;
+        component.miniLogoData = null;
+        component.faviconData = null;
+
+        component.saveLogos();
+
+        httpTestingController.expectNone((req: { method: string }) => 
req.method === "PUT");
+        expect(msgSuccess).not.toHaveBeenCalled();
+      });
+
+      it("saveLogos notifies an error when the request fails", () => {
+        completeLoad();
+        component.logoData = "logo.png";
+        component.miniLogoData = null;
+        component.faviconData = null;
+
+        component.saveLogos();
+
+        httpTestingController.expectOne(updateUrl("logo")).flush("boom", 
HTTP_ERROR);
+        expect(msgError).toHaveBeenCalledWith("Failed to save branding.");
+      });
+
+      it("resetBranding POSTs a reset for all three branding settings", () => {
+        completeLoad();
+
+        component.resetBranding();
+
+        ["logo", "mini_logo", "favicon"].forEach(key => {
+          const req = httpTestingController.expectOne(resetUrl(key));
+          expect(req.request.method).toBe("POST");
+          req.flush(null);
+        });
+        expect(msgInfo).toHaveBeenCalledWith("Resetting branding...");
+      });
+    });
+
+    describe("tabs", () => {
+      it("saveTabs PUTs every sidebar tab and notifies success", () => {
+        completeLoad();
+
+        component.saveTabs();
+
+        Object.keys(component.sidebarTabs).forEach(tab => {
+          const req = httpTestingController.expectOne(updateUrl(tab));
+          expect(req.request.method).toBe("PUT");
+          req.flush(null);
+        });
+        expect(msgSuccess).toHaveBeenCalledWith("Tabs saved successfully.");
+      });
+
+      it("saveTabs notifies an error when a request fails", () => {
+        completeLoad();
+
+        component.saveTabs();
+
+        const firstTab = Object.keys(component.sidebarTabs)[0];
+        httpTestingController.expectOne(updateUrl(firstTab)).flush("boom", 
HTTP_ERROR);
+        expect(msgError).toHaveBeenCalledWith("Failed to save tabs.");
+      });
+
+      it("resetTabs POSTs a reset for every sidebar tab", () => {
+        completeLoad();
+
+        component.resetTabs();
+
+        Object.keys(component.sidebarTabs).forEach(tab => {
+          httpTestingController.expectOne(resetUrl(tab)).flush(null);
+        });
+        expect(msgInfo).toHaveBeenCalledWith("Resetting tabs...");
+      });
+    });
+
+    describe("dataset settings", () => {
+      it("saveDatasetSettings PUTs the four upload settings and notifies 
success", () => {
+        completeLoad(); // defaults (20 / 3 / 10 / 50) are valid
+
+        component.saveDatasetSettings();
+
+        const expectPut = (key: string, value: string) => {
+          const req = httpTestingController.expectOne(updateUrl(key));
+          expect(req.request.method).toBe("PUT");
+          expect(req.request.body).toEqual({ value });
+          req.flush(null);
+        };
+        expectPut("max_number_of_concurrent_uploading_file", "3");
+        expectPut("single_file_upload_max_size_mib", "20");
+        expectPut("max_number_of_concurrent_uploading_file_chunks", "10");
+        expectPut("multipart_upload_chunk_size_mib", "50");
+
+        expect(msgSuccess).toHaveBeenCalledWith("Dataset upload settings saved 
successfully.");
+      });
+
+      it("saveDatasetSettings rejects non-positive values without saving", () 
=> {
+        completeLoad();
+        component.maxFileSizeMiB = 0;
+
+        component.saveDatasetSettings();
+
+        httpTestingController.expectNone((req: { method: string }) => 
req.method === "PUT");
+        expect(msgError).toHaveBeenCalledWith("Please enter valid integer 
values.");
+      });
+
+      it("saveDatasetSettings rejects a configuration that would exceed the 
10,000-part limit", () => {
+        completeLoad();
+        component.maxFileSizeMiB = 100000;
+        component.chunkSizeMiB = 1;
+
+        component.saveDatasetSettings();
+
+        httpTestingController.expectNone((req: { method: string }) => 
req.method === "PUT");
+        expect(msgError).toHaveBeenCalled();
+      });
+
+      it("saveDatasetSettings notifies an error when a request fails", () => {
+        completeLoad();
+
+        component.saveDatasetSettings();
+
+        
httpTestingController.expectOne(updateUrl("max_number_of_concurrent_uploading_file")).flush("boom",
 HTTP_ERROR);
+        expect(msgError).toHaveBeenCalledWith("Failed to save dataset 
settings.");
+      });
+
+      it("resetDatasetSettings POSTs a reset for all four upload settings", () 
=> {
+        completeLoad();
+
+        component.resetDatasetSettings();
+
+        [
+          "max_number_of_concurrent_uploading_file",
+          "single_file_upload_max_size_mib",
+          "max_number_of_concurrent_uploading_file_chunks",
+          "multipart_upload_chunk_size_mib",
+        ].forEach(key => 
httpTestingController.expectOne(resetUrl(key)).flush(null));
+        expect(msgInfo).toHaveBeenCalledWith("Resetting dataset settings...");
+      });
+    });
+
+    describe("csv (result panel) settings", () => {
+      it("saveCsvSettings PUTs the max-columns value and notifies success", () 
=> {
+        completeLoad();
+        component.csvMaxColumns = 256;
+
+        component.saveCsvSettings();
+
+        const req = 
httpTestingController.expectOne(updateUrl("csv_parser_max_columns"));
+        expect(req.request.method).toBe("PUT");
+        expect(req.request.body).toEqual({ value: "256" });
+        req.flush(null);
+
+        expect(notifySuccess).toHaveBeenCalledWith("Result panel settings 
saved.");
+      });
+
+      it("saveCsvSettings notifies an error when the request fails", () => {
+        completeLoad();
+
+        component.saveCsvSettings();
+
+        
httpTestingController.expectOne(updateUrl("csv_parser_max_columns")).flush("boom",
 HTTP_ERROR);
+        expect(notifyError).toHaveBeenCalledWith("Could not save result panel 
settings.");
+      });
+
+      it("resetCsvSettings POSTs a reset and notifies info", () => {
+        completeLoad();
+
+        component.resetCsvSettings();
+
+        
httpTestingController.expectOne(resetUrl("csv_parser_max_columns")).flush(null);
+        expect(notifyInfo).toHaveBeenCalledWith("Resetting result panel 
settings...");
+      });
+    });
+
+    describe("onFileChange", () => {
+      it("rejects a non-image file with an error and leaves the existing logo 
untouched", () => {
+        completeLoad();
+        component.logoData = "data:image/png;base64,EXISTING";
+        const event = {
+          target: { files: [new File(["x"], "notes.txt", { type: "text/plain" 
})] },
+        } as unknown as Event;
+
+        component.onFileChange("logo", event);
+
+        expect(msgError).toHaveBeenCalledWith("Please upload a valid image 
file.");
+        expect(component.logoData).toBe("data:image/png;base64,EXISTING");
+      });
+
+      it("reads a valid image file into the matching branding field", async () 
=> {
+        completeLoad();
+        const dataUrl = "data:image/png;base64,AAA";
+        class FakeFileReader {
+          onload: ((e: { target: { result: string } }) => void) | null = null;
+          readAsDataURL(): void {
+            queueMicrotask(() => this.onload?.({ target: { result: dataUrl } 
}));
+          }
+        }
+        const OriginalFileReader = globalThis.FileReader;
+        globalThis.FileReader = FakeFileReader as unknown as typeof FileReader;
+
+        try {
+          const event = {
+            target: { files: [new File(["x"], "logo.png", { type: "image/png" 
})] },
+          } as unknown as Event;
+
+          component.onFileChange("mini_logo", event);
+          await Promise.resolve();
+
+          expect(component.miniLogoData).toBe(dataUrl);
+        } finally {
+          globalThis.FileReader = OriginalFileReader;
+        }

Review Comment:
   Renamed `OriginalFileReader` → `realFileReader` and switched the assignment 
to the `(globalThis as any)` cast, matching the other specs.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to