From 0725a5373b5b70ecedf7b7636e2785cbfda6b0e4 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 26 Jul 2025 07:20:35 +0000 Subject: [PATCH 1/4] Initial plan From f54660e95d02eb4171b38e5e9d682dda2169cfcd Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 26 Jul 2025 07:35:59 +0000 Subject: [PATCH 2/4] Implement localStorage persistence for HAR viewer mode selection Co-authored-by: peckz <18050177+peckz@users.noreply.github.com> --- .../pages/utilities/har-file-viewer.test.tsx | 98 +++++++++++++++++++ pages/utilities/har-file-viewer.tsx | 27 +++++ 2 files changed, 125 insertions(+) diff --git a/__tests__/pages/utilities/har-file-viewer.test.tsx b/__tests__/pages/utilities/har-file-viewer.test.tsx index ef2bbb7..a5ea095 100644 --- a/__tests__/pages/utilities/har-file-viewer.test.tsx +++ b/__tests__/pages/utilities/har-file-viewer.test.tsx @@ -50,6 +50,11 @@ const mockHarData = { }; describe("HARFileViewer", () => { + beforeEach(() => { + // Clear localStorage before each test + localStorage.clear(); + }); + test("should render the component and display the drop zone text", () => { render(); @@ -121,4 +126,97 @@ describe("HARFileViewer", () => { const row1 = within(rows[0]).getByTestId("column-status-code"); expect(row1).toHaveTextContent("200"); }); + + test("should default to table view when no localStorage value exists", async () => { + // Clear localStorage to ensure we test the default behavior + localStorage.clear(); + + const user = userEvent.setup(); + render(); + + // Upload a HAR file first to show the view buttons + const file = new File([JSON.stringify(mockHarData)], "test.har", { + type: "application/json", + }); + const fileInput = screen.getByTestId("input"); + await user.upload(fileInput, file); + + // Wait for the table rows to appear (this means HAR is loaded) + const rows = await screen.findAllByTestId("table-row"); + expect(rows).toHaveLength(2); + + // Check that localStorage has the default table view saved + expect(localStorage.getItem("har-viewer-view-mode")).toBe("table"); + }); + + test("should load waterfall view from localStorage if previously saved", async () => { + // Set localStorage to waterfall view + localStorage.setItem("har-viewer-view-mode", "waterfall"); + + const user = userEvent.setup(); + render(); + + // Upload a HAR file + const file = new File([JSON.stringify(mockHarData)], "test.har", { + type: "application/json", + }); + const fileInput = screen.getByTestId("input"); + await user.upload(fileInput, file); + + // Wait for HAR data to load - in waterfall view we should see the HarWaterfall component + // Since waterfall view doesn't show table rows, we need to check for the component differently + // Let's wait for the filter buttons to appear which appear in both views + await screen.findByRole("button", { name: "All" }); + + // Verify localStorage still has waterfall + expect(localStorage.getItem("har-viewer-view-mode")).toBe("waterfall"); + }); + + test("should save view mode to localStorage when user switches views", async () => { + localStorage.clear(); + + const user = userEvent.setup(); + render(); + + // Upload a HAR file first + const file = new File([JSON.stringify(mockHarData)], "test.har", { + type: "application/json", + }); + const fileInput = screen.getByTestId("input"); + await user.upload(fileInput, file); + + // Wait for the table rows to appear (means we're in table view initially) + const rows = await screen.findAllByTestId("table-row"); + expect(rows).toHaveLength(2); + + // Initially should be table view + expect(localStorage.getItem("har-viewer-view-mode")).toBe("table"); + + // Click waterfall button + const waterfallButton = screen.getByRole("button", { name: /waterfall/i }); + await user.click(waterfallButton); + + // Should save to localStorage + expect(localStorage.getItem("har-viewer-view-mode")).toBe("waterfall"); + + // Click table button + const tableButton = screen.getByRole("button", { name: /table view/i }); + await user.click(tableButton); + + // Should save to localStorage and we should see table rows again + expect(localStorage.getItem("har-viewer-view-mode")).toBe("table"); + await screen.findAllByTestId("table-row"); + }); + + test("should handle localStorage errors gracefully", () => { + // Mock localStorage to throw an error + const mockGetItem = jest.spyOn(Storage.prototype, "getItem").mockImplementation(() => { + throw new Error("localStorage not available"); + }); + + // Should not crash when localStorage throws + expect(() => render()).not.toThrow(); + + mockGetItem.mockRestore(); + }); }); diff --git a/pages/utilities/har-file-viewer.tsx b/pages/utilities/har-file-viewer.tsx index 3a8b388..7fdf791 100644 --- a/pages/utilities/har-file-viewer.tsx +++ b/pages/utilities/har-file-viewer.tsx @@ -110,6 +110,33 @@ export default function HARFileViewer() { const [activeFilter, setActiveFilter] = useState("All"); const [viewMode, setViewMode] = useState<"table" | "waterfall">("table"); const [statusFilter, setStatusFilter] = useState([]); + const [isInitialized, setIsInitialized] = useState(false); + + // Load view mode from localStorage on component mount + useEffect(() => { + try { + const savedViewMode = localStorage.getItem("har-viewer-view-mode"); + if (savedViewMode === "table" || savedViewMode === "waterfall") { + setViewMode(savedViewMode); + } + } catch (error) { + // localStorage not available or error occurred, use default + console.warn("Failed to load view mode from localStorage:", error); + } + setIsInitialized(true); + }, []); + + // Save view mode to localStorage when it changes (but not on initial load) + useEffect(() => { + if (!isInitialized) return; + + try { + localStorage.setItem("har-viewer-view-mode", viewMode); + } catch (error) { + // localStorage not available or error occurred + console.warn("Failed to save view mode to localStorage:", error); + } + }, [viewMode, isInitialized]); const handleFileUpload = useCallback((file: File | undefined) => { if (!file) { From f287be146081bf4f2548745f075d771024637b79 Mon Sep 17 00:00:00 2001 From: peckz Date: Sat, 26 Jul 2025 10:22:22 +0200 Subject: [PATCH 3/4] chore: fix formatting --- .../pages/utilities/har-file-viewer.test.tsx | 42 ++++++++++--------- pages/utilities/har-file-viewer.tsx | 7 +++- 2 files changed, 28 insertions(+), 21 deletions(-) diff --git a/__tests__/pages/utilities/har-file-viewer.test.tsx b/__tests__/pages/utilities/har-file-viewer.test.tsx index a5ea095..f797172 100644 --- a/__tests__/pages/utilities/har-file-viewer.test.tsx +++ b/__tests__/pages/utilities/har-file-viewer.test.tsx @@ -58,7 +58,9 @@ describe("HARFileViewer", () => { test("should render the component and display the drop zone text", () => { render(); - expect(screen.getByText("Drop your .har or .json file here")).toBeInTheDocument(); + expect( + screen.getByText("Drop your .har or .json file here") + ).toBeInTheDocument(); }); test("should list all requests after uploading a har file", async () => { @@ -130,10 +132,10 @@ describe("HARFileViewer", () => { test("should default to table view when no localStorage value exists", async () => { // Clear localStorage to ensure we test the default behavior localStorage.clear(); - + const user = userEvent.setup(); render(); - + // Upload a HAR file first to show the view buttons const file = new File([JSON.stringify(mockHarData)], "test.har", { type: "application/json", @@ -144,7 +146,7 @@ describe("HARFileViewer", () => { // Wait for the table rows to appear (this means HAR is loaded) const rows = await screen.findAllByTestId("table-row"); expect(rows).toHaveLength(2); - + // Check that localStorage has the default table view saved expect(localStorage.getItem("har-viewer-view-mode")).toBe("table"); }); @@ -152,10 +154,10 @@ describe("HARFileViewer", () => { test("should load waterfall view from localStorage if previously saved", async () => { // Set localStorage to waterfall view localStorage.setItem("har-viewer-view-mode", "waterfall"); - + const user = userEvent.setup(); render(); - + // Upload a HAR file const file = new File([JSON.stringify(mockHarData)], "test.har", { type: "application/json", @@ -167,17 +169,17 @@ describe("HARFileViewer", () => { // Since waterfall view doesn't show table rows, we need to check for the component differently // Let's wait for the filter buttons to appear which appear in both views await screen.findByRole("button", { name: "All" }); - + // Verify localStorage still has waterfall expect(localStorage.getItem("har-viewer-view-mode")).toBe("waterfall"); }); test("should save view mode to localStorage when user switches views", async () => { localStorage.clear(); - + const user = userEvent.setup(); render(); - + // Upload a HAR file first const file = new File([JSON.stringify(mockHarData)], "test.har", { type: "application/json", @@ -188,21 +190,21 @@ describe("HARFileViewer", () => { // Wait for the table rows to appear (means we're in table view initially) const rows = await screen.findAllByTestId("table-row"); expect(rows).toHaveLength(2); - + // Initially should be table view expect(localStorage.getItem("har-viewer-view-mode")).toBe("table"); - + // Click waterfall button const waterfallButton = screen.getByRole("button", { name: /waterfall/i }); await user.click(waterfallButton); - + // Should save to localStorage expect(localStorage.getItem("har-viewer-view-mode")).toBe("waterfall"); - + // Click table button const tableButton = screen.getByRole("button", { name: /table view/i }); await user.click(tableButton); - + // Should save to localStorage and we should see table rows again expect(localStorage.getItem("har-viewer-view-mode")).toBe("table"); await screen.findAllByTestId("table-row"); @@ -210,13 +212,15 @@ describe("HARFileViewer", () => { test("should handle localStorage errors gracefully", () => { // Mock localStorage to throw an error - const mockGetItem = jest.spyOn(Storage.prototype, "getItem").mockImplementation(() => { - throw new Error("localStorage not available"); - }); - + const mockGetItem = jest + .spyOn(Storage.prototype, "getItem") + .mockImplementation(() => { + throw new Error("localStorage not available"); + }); + // Should not crash when localStorage throws expect(() => render()).not.toThrow(); - + mockGetItem.mockRestore(); }); }); diff --git a/pages/utilities/har-file-viewer.tsx b/pages/utilities/har-file-viewer.tsx index 7fdf791..b0fdcc4 100644 --- a/pages/utilities/har-file-viewer.tsx +++ b/pages/utilities/har-file-viewer.tsx @@ -129,7 +129,7 @@ export default function HARFileViewer() { // Save view mode to localStorage when it changes (but not on initial load) useEffect(() => { if (!isInitialized) return; - + try { localStorage.setItem("har-viewer-view-mode", viewMode); } catch (error) { @@ -169,7 +169,10 @@ export default function HARFileViewer() { setStatus("hover"); const file = event.dataTransfer.files[0]; - if (!file || (!file.name.endsWith(".har") && !file.name.endsWith(".json"))) { + if ( + !file || + (!file.name.endsWith(".har") && !file.name.endsWith(".json")) + ) { setStatus("unsupported"); return; } From 926f897f192a01c371eb929b1a03f177fe3fa1ce Mon Sep 17 00:00:00 2001 From: peckz Date: Sat, 26 Jul 2025 10:28:11 +0200 Subject: [PATCH 4/4] chore: remove local storage tests --- .../pages/utilities/har-file-viewer.test.tsx | 100 ------------------ pages/utilities/har-file-viewer.tsx | 2 - 2 files changed, 102 deletions(-) diff --git a/__tests__/pages/utilities/har-file-viewer.test.tsx b/__tests__/pages/utilities/har-file-viewer.test.tsx index f797172..8a62239 100644 --- a/__tests__/pages/utilities/har-file-viewer.test.tsx +++ b/__tests__/pages/utilities/har-file-viewer.test.tsx @@ -50,11 +50,6 @@ const mockHarData = { }; describe("HARFileViewer", () => { - beforeEach(() => { - // Clear localStorage before each test - localStorage.clear(); - }); - test("should render the component and display the drop zone text", () => { render(); @@ -128,99 +123,4 @@ describe("HARFileViewer", () => { const row1 = within(rows[0]).getByTestId("column-status-code"); expect(row1).toHaveTextContent("200"); }); - - test("should default to table view when no localStorage value exists", async () => { - // Clear localStorage to ensure we test the default behavior - localStorage.clear(); - - const user = userEvent.setup(); - render(); - - // Upload a HAR file first to show the view buttons - const file = new File([JSON.stringify(mockHarData)], "test.har", { - type: "application/json", - }); - const fileInput = screen.getByTestId("input"); - await user.upload(fileInput, file); - - // Wait for the table rows to appear (this means HAR is loaded) - const rows = await screen.findAllByTestId("table-row"); - expect(rows).toHaveLength(2); - - // Check that localStorage has the default table view saved - expect(localStorage.getItem("har-viewer-view-mode")).toBe("table"); - }); - - test("should load waterfall view from localStorage if previously saved", async () => { - // Set localStorage to waterfall view - localStorage.setItem("har-viewer-view-mode", "waterfall"); - - const user = userEvent.setup(); - render(); - - // Upload a HAR file - const file = new File([JSON.stringify(mockHarData)], "test.har", { - type: "application/json", - }); - const fileInput = screen.getByTestId("input"); - await user.upload(fileInput, file); - - // Wait for HAR data to load - in waterfall view we should see the HarWaterfall component - // Since waterfall view doesn't show table rows, we need to check for the component differently - // Let's wait for the filter buttons to appear which appear in both views - await screen.findByRole("button", { name: "All" }); - - // Verify localStorage still has waterfall - expect(localStorage.getItem("har-viewer-view-mode")).toBe("waterfall"); - }); - - test("should save view mode to localStorage when user switches views", async () => { - localStorage.clear(); - - const user = userEvent.setup(); - render(); - - // Upload a HAR file first - const file = new File([JSON.stringify(mockHarData)], "test.har", { - type: "application/json", - }); - const fileInput = screen.getByTestId("input"); - await user.upload(fileInput, file); - - // Wait for the table rows to appear (means we're in table view initially) - const rows = await screen.findAllByTestId("table-row"); - expect(rows).toHaveLength(2); - - // Initially should be table view - expect(localStorage.getItem("har-viewer-view-mode")).toBe("table"); - - // Click waterfall button - const waterfallButton = screen.getByRole("button", { name: /waterfall/i }); - await user.click(waterfallButton); - - // Should save to localStorage - expect(localStorage.getItem("har-viewer-view-mode")).toBe("waterfall"); - - // Click table button - const tableButton = screen.getByRole("button", { name: /table view/i }); - await user.click(tableButton); - - // Should save to localStorage and we should see table rows again - expect(localStorage.getItem("har-viewer-view-mode")).toBe("table"); - await screen.findAllByTestId("table-row"); - }); - - test("should handle localStorage errors gracefully", () => { - // Mock localStorage to throw an error - const mockGetItem = jest - .spyOn(Storage.prototype, "getItem") - .mockImplementation(() => { - throw new Error("localStorage not available"); - }); - - // Should not crash when localStorage throws - expect(() => render()).not.toThrow(); - - mockGetItem.mockRestore(); - }); }); diff --git a/pages/utilities/har-file-viewer.tsx b/pages/utilities/har-file-viewer.tsx index b0fdcc4..706740e 100644 --- a/pages/utilities/har-file-viewer.tsx +++ b/pages/utilities/har-file-viewer.tsx @@ -121,7 +121,6 @@ export default function HARFileViewer() { } } catch (error) { // localStorage not available or error occurred, use default - console.warn("Failed to load view mode from localStorage:", error); } setIsInitialized(true); }, []); @@ -134,7 +133,6 @@ export default function HARFileViewer() { localStorage.setItem("har-viewer-view-mode", viewMode); } catch (error) { // localStorage not available or error occurred - console.warn("Failed to save view mode to localStorage:", error); } }, [viewMode, isInitialized]);