From b04f52efdb6dd91490b018bfc3479e3b9a8a9fcb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Castillo?= Date: Thu, 5 Feb 2026 18:13:30 -0300 Subject: [PATCH 1/6] fix: restore location when the user navigates from a nested route to a new tab MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Tomás Castillo --- src/pages/sponsors/edit-sponsor-page.js | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/pages/sponsors/edit-sponsor-page.js b/src/pages/sponsors/edit-sponsor-page.js index d3101dc25..8fc2c35af 100644 --- a/src/pages/sponsors/edit-sponsor-page.js +++ b/src/pages/sponsors/edit-sponsor-page.js @@ -122,7 +122,16 @@ const EditSponsorPage = (props) => { const handleTabChange = (event, newValue) => { setSelectedTab(newValue); - window.location.hash = getFragmentFromValue(newValue); + + const basePath = `/app/summits/${currentSummit.id}/sponsors/${entity.id}`; + const fragment = getFragmentFromValue(newValue); + + // restore location if it comes from a nested route + if (location.pathname !== basePath) { + history.push(`${basePath}#${fragment}`); + } else { + window.location.hash = fragment; + } }; useEffect(() => { From a8672e8aa3c6687b71713e536f637d0f420f80a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Castillo?= Date: Thu, 5 Feb 2026 20:41:17 -0300 Subject: [PATCH 2/6] fix: adjust tests and add new case for nested routes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Tomás Castillo --- .../__tests__/edit-sponsor-page.test.js | 105 ++++++++++++++++-- .../sponsor-page-forms-list-reducer.js | 2 +- 2 files changed, 95 insertions(+), 12 deletions(-) diff --git a/src/pages/sponsors/__tests__/edit-sponsor-page.test.js b/src/pages/sponsors/__tests__/edit-sponsor-page.test.js index f6a1367d7..ed3db55ea 100644 --- a/src/pages/sponsors/__tests__/edit-sponsor-page.test.js +++ b/src/pages/sponsors/__tests__/edit-sponsor-page.test.js @@ -7,6 +7,7 @@ import EditSponsorPage, { } from "../edit-sponsor-page"; import { renderWithRedux } from "../../../utils/test-utils"; import { DEFAULT_STATE as currentSponsorDefaultState } from "../../../reducers/sponsors/sponsor-reducer"; +import { DEFAULT_STATE as currentSponsorFormsDefaultState } from "../../../reducers/sponsors/sponsor-page-forms-list-reducer"; import { DEFAULT_ENTITY as defaultSummitEntity, DEFAULT_STATE as currentSummitDefaultState @@ -18,6 +19,17 @@ jest.mock( ); jest.mock("../sponsor-users-list-per-sponsor/index.js"); +jest.mock("../../../actions/sponsor-actions", () => ({ + ...jest.requireActual("../../../actions/sponsor-actions"), + getSponsorAdvertisements: jest.fn(() => ({ type: "MOCK_ACTION" })), + getSponsorMaterials: jest.fn(() => ({ type: "MOCK_ACTION" })), + getSponsorSocialNetworks: jest.fn(() => ({ type: "MOCK_ACTION" })), + getSponsorLeadReportSettingsMeta: jest.fn(() => ({ type: "MOCK_ACTION" })), + getSponsorTiers: jest.fn(() => ({ type: "MOCK_ACTION" })), + getExtraQuestionMeta: jest.fn(() => ({ type: "MOCK_ACTION" })), + resetSponsorForm: jest.fn(() => ({ type: "MOCK_ACTION" })) +})); + describe("EditSponsorPage", () => { describe("getFragmentFromValue", () => { it("returns correct values", () => { @@ -65,7 +77,7 @@ describe("EditSponsorPage", () => { describe("Component", () => { const originalWindowLocation = window.location; - it("should change the url fragment on tab click", async () => { + it("should change the url fragment on tab click (same path)", async () => { delete window.location; Object.defineProperty(window, "location", { @@ -73,29 +85,97 @@ describe("EditSponsorPage", () => { writable: true, value: { ...originalWindowLocation, - hash: "#general" + hash: "#general", + pathname: "/app/summits/12/sponsors/123" } }); + const mockHistory = { push: jest.fn() }; + renderWithRedux( , { initialState: { currentSummitState: { - currentSummit: defaultSummitEntity, - ...currentSummitDefaultState + ...currentSummitDefaultState, + currentSummit: { ...defaultSummitEntity, id: 12 } }, loggedUserState: { member: { groups: {} } }, + currentSummitSponsorshipListState: { + sponsorships: { + sponsorships: [], + currentPage: 1, + lastPage: 1, + perPage: 100, + order: "order", + orderDir: 1, + totalSponsorships: 0 + } + }, + currentSponsorState: { + ...currentSponsorDefaultState, + entity: { ...currentSponsorDefaultState.entity, id: 123 } + }, + sponsorPageFormsListState: { + ...currentSponsorFormsDefaultState + } + } + } + ); + + const usersTabReference = screen.getByText("edit_sponsor.tab.forms"); + + await act(async () => { + await userEvent.click(usersTabReference); + }); + + expect(window.location.hash).toBe("forms"); + expect(mockHistory.push).not.toHaveBeenCalled(); + }); + + it("should call history.push on tab click when on nested route", async () => { + delete window.location; + + Object.defineProperty(window, "location", { + configurable: true, + writable: true, + value: { + ...originalWindowLocation, + hash: "#forms", + pathname: "/app/summits/12/sponsors/44/sponsor-forms/15/items", + replace: jest.fn() + } + }); + + const mockHistory = { push: jest.fn() }; + + renderWithRedux( + , + { + initialState: { + currentSummitState: { + ...currentSummitDefaultState, + currentSummit: { ...defaultSummitEntity, id: 12 } + }, + loggedUserState: { + member: { groups: {} } + }, currentSummitSponsorshipListState: { sponsorships: [], currentPage: 1, @@ -106,20 +186,23 @@ describe("EditSponsorPage", () => { totalSponsorships: 0 }, currentSponsorState: { - sponsorships: [], - ...currentSponsorDefaultState + ...currentSponsorDefaultState, + entity: { ...currentSponsorDefaultState.entity, id: 44 } } } } ); - const usersTabReference = screen.getByText("edit_sponsor.tab.forms"); + const usersTab = screen.getByText("edit_sponsor.tab.users"); await act(async () => { - await userEvent.click(usersTabReference); + await userEvent.click(usersTab); }); - expect(window.location.hash).toBe("forms"); + // nested route, so uses history.push + expect(mockHistory.push).toHaveBeenCalledWith( + "/app/summits/12/sponsors/44#users" + ); }); it("should change the tab rendered on fragment change", async () => { diff --git a/src/reducers/sponsors/sponsor-page-forms-list-reducer.js b/src/reducers/sponsors/sponsor-page-forms-list-reducer.js index 3f6467ccf..40c5cacc0 100644 --- a/src/reducers/sponsors/sponsor-page-forms-list-reducer.js +++ b/src/reducers/sponsors/sponsor-page-forms-list-reducer.js @@ -26,7 +26,7 @@ import { } from "../../actions/sponsor-forms-actions"; import { SET_CURRENT_SUMMIT } from "../../actions/summit-actions"; -const DEFAULT_STATE = { +export const DEFAULT_STATE = { managedForms: { forms: [], order: "name", From 178d3350ed97dcce25bdded5c4c94fd5b366bd72 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Castillo?= Date: Fri, 6 Feb 2026 18:32:12 -0300 Subject: [PATCH 3/6] fix: address comments, adjust navigation using history and react router MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Tomás Castillo --- src/layouts/sponsor-id-layout.js | 12 +++++- src/pages/sponsors/edit-sponsor-page.js | 43 +++++++++++-------- src/pages/sponsors/sponsor-forms-tab/index.js | 2 +- 3 files changed, 36 insertions(+), 21 deletions(-) diff --git a/src/layouts/sponsor-id-layout.js b/src/layouts/sponsor-id-layout.js index 4b117c411..d39a3d32c 100644 --- a/src/layouts/sponsor-id-layout.js +++ b/src/layouts/sponsor-id-layout.js @@ -145,7 +145,17 @@ class SponsorIdLayout extends React.Component { ( +
+ + +
+ )} /> diff --git a/src/pages/sponsors/edit-sponsor-page.js b/src/pages/sponsors/edit-sponsor-page.js index 8fc2c35af..c60f68fd0 100644 --- a/src/pages/sponsors/edit-sponsor-page.js +++ b/src/pages/sponsors/edit-sponsor-page.js @@ -59,8 +59,8 @@ export const tabsToFragmentMap = [ export const getFragmentFromValue = (index) => tabsToFragmentMap[index]; -export const getTabFromUrlFragment = () => { - const currentHash = window.location.hash.replace("#", ""); +export const getTabFromFragment = (location) => { + const currentHash = (location.hash || "").replace("#", ""); const result = tabsToFragmentMap.indexOf(currentHash); if (result > -1) return result; return 0; @@ -118,28 +118,36 @@ const EditSponsorPage = (props) => { getExtraQuestionMeta } = props; - const [selectedTab, setSelectedTab] = useState(getTabFromUrlFragment()); + const [selectedTab, setSelectedTab] = useState(getTabFromFragment(location)); + + const isNestedFormItemRoute = !!match.params?.form_id; const handleTabChange = (event, newValue) => { setSelectedTab(newValue); - - const basePath = `/app/summits/${currentSummit.id}/sponsors/${entity.id}`; const fragment = getFragmentFromValue(newValue); - - // restore location if it comes from a nested route - if (location.pathname !== basePath) { - history.push(`${basePath}#${fragment}`); + if (isNestedFormItemRoute) { + history.push( + `/app/summits/${currentSummit.id}/sponsors/${entity.id}#${fragment}` + ); } else { - window.location.hash = fragment; + history.replace({ ...location, hash: `#${fragment}` }); } }; useEffect(() => { - const onHashChange = () => setSelectedTab(getTabFromUrlFragment()); - window.addEventListener("hashchange", onHashChange); - // default call - if (!window.location.hash) handleTabChange(null, getTabFromUrlFragment()); - return () => window.removeEventListener("hashchange", onHashChange); + setSelectedTab(getTabFromFragment(location)); + }, [location.hash]); + + useEffect(() => { + if (!location.hash) { + const defaultTab = isNestedFormItemRoute + ? SPONSOR_TABS.FORMS + : SPONSOR_TABS.GENERAL; + history.replace({ + ...location, + hash: `#${getFragmentFromValue(defaultTab)}` + }); + } }, []); useEffect(() => { @@ -182,9 +190,7 @@ const EditSponsorPage = (props) => { } ]; - const sponsorFormItemRoute = - location.pathname.includes("/sponsor-forms/") && - location.pathname.includes("/items"); + const sponsorFormItemRoute = !!match.params?.form_id; return ( @@ -205,7 +211,6 @@ const EditSponsorPage = (props) => { key={t.value} label={t.label} value={t.value} - onClick={() => handleTabChange(null, t.value)} sx={{ fontSize: "1.4rem", lineHeight: "1.8rem", diff --git a/src/pages/sponsors/sponsor-forms-tab/index.js b/src/pages/sponsors/sponsor-forms-tab/index.js index dccaeaabc..1b462c230 100644 --- a/src/pages/sponsors/sponsor-forms-tab/index.js +++ b/src/pages/sponsors/sponsor-forms-tab/index.js @@ -113,7 +113,7 @@ const SponsorFormsTab = ({ const handleManageItems = (item) => { history.push( - `/app/summits/${summitId}/sponsors/${sponsor.id}/sponsor-forms/${item.id}/items` + `/app/summits/${summitId}/sponsors/${sponsor.id}/sponsor-forms/${item.id}/items#forms` ); }; From 6e61e0aa2447b857d408dfe184eb3dabe78d64e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Castillo?= Date: Fri, 6 Feb 2026 19:07:15 -0300 Subject: [PATCH 4/6] fix: update tests with history and react-router MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Tomás Castillo --- .../__tests__/edit-sponsor-page.test.js | 115 ++++-------------- 1 file changed, 27 insertions(+), 88 deletions(-) diff --git a/src/pages/sponsors/__tests__/edit-sponsor-page.test.js b/src/pages/sponsors/__tests__/edit-sponsor-page.test.js index ed3db55ea..10693623e 100644 --- a/src/pages/sponsors/__tests__/edit-sponsor-page.test.js +++ b/src/pages/sponsors/__tests__/edit-sponsor-page.test.js @@ -3,7 +3,7 @@ import userEvent from "@testing-library/user-event"; import { act, screen } from "@testing-library/react"; import EditSponsorPage, { getFragmentFromValue, - getTabFromUrlFragment + getTabFromFragment } from "../edit-sponsor-page"; import { renderWithRedux } from "../../../utils/test-utils"; import { DEFAULT_STATE as currentSponsorDefaultState } from "../../../reducers/sponsors/sponsor-reducer"; @@ -13,7 +13,6 @@ import { DEFAULT_STATE as currentSummitDefaultState } from "../../../reducers/summits/current-summit-reducer"; -global.window = { location: { pathname: "/sponsor-forms/items" } }; jest.mock( "../sponsor-forms-tab/components/manage-items/sponsor-forms-manage-items.js" ); @@ -47,50 +46,20 @@ describe("EditSponsorPage", () => { }); }); - describe("getTabFromUrlFragment", () => { + describe("getTabFromFragment", () => { it("returns correct values for defined fragments", () => { - const newUrl1 = "#general"; - window.location.hash = newUrl1; - - const result1 = getTabFromUrlFragment(); - expect(result1).toBe(0); - - const newUrl2 = "#pages"; - window.location.hash = newUrl2; - - const result2 = getTabFromUrlFragment(); - expect(result2).toBe(2); - - const newUrl3 = "#media_uploads"; - window.location.hash = newUrl3; - - const result3 = getTabFromUrlFragment(); - expect(result3).toBe(3); - - const newUrl4 = "#badge_scans"; - window.location.hash = newUrl4; - - const result4 = getTabFromUrlFragment(); - expect(result4).toBe(7); + expect(getTabFromFragment({ hash: "#general" })).toBe(0); + expect(getTabFromFragment({ hash: "#users" })).toBe(1); + expect(getTabFromFragment({ hash: "#pages" })).toBe(2); + expect(getTabFromFragment({ hash: "#media_uploads" })).toBe(3); + expect(getTabFromFragment({ hash: "#forms" })).toBe(4); + expect(getTabFromFragment({ hash: "#badge_scans" })).toBe(7); }); }); describe("Component", () => { - const originalWindowLocation = window.location; it("should change the url fragment on tab click (same path)", async () => { - delete window.location; - - Object.defineProperty(window, "location", { - configurable: true, - writable: true, - value: { - ...originalWindowLocation, - hash: "#general", - pathname: "/app/summits/12/sponsors/123" - } - }); - - const mockHistory = { push: jest.fn() }; + const mockHistory = { push: jest.fn(), replace: jest.fn() }; renderWithRedux( { await userEvent.click(usersTabReference); }); - expect(window.location.hash).toBe("forms"); + expect(mockHistory.replace).toHaveBeenCalledWith( + expect.objectContaining({ + hash: "#forms" + }) + ); expect(mockHistory.push).not.toHaveBeenCalled(); }); it("should call history.push on tab click when on nested route", async () => { - delete window.location; - - Object.defineProperty(window, "location", { - configurable: true, - writable: true, - value: { - ...originalWindowLocation, - hash: "#forms", - pathname: "/app/summits/12/sponsors/44/sponsor-forms/15/items", - replace: jest.fn() - } - }); - - const mockHistory = { push: jest.fn() }; + const mockHistory = { push: jest.fn(), replace: jest.fn() }; renderWithRedux( { location={{ pathname: "/app/summits/12/sponsors/44/sponsor-forms/15/items" }} - match={{}} + match={{ params: { form_id: 15 } }} />, { initialState: { @@ -199,29 +159,18 @@ describe("EditSponsorPage", () => { await userEvent.click(usersTab); }); - // nested route, so uses history.push expect(mockHistory.push).toHaveBeenCalledWith( "/app/summits/12/sponsors/44#users" ); }); - it("should change the tab rendered on fragment change", async () => { - delete window.location; - - Object.defineProperty(window, "location", { - configurable: true, - writable: true, - value: { - ...originalWindowLocation, - hash: "#general" - } - }); - - renderWithRedux( + it("should change the tab rendered on fragment change", () => { + const { rerender } = renderWithRedux( , @@ -256,26 +205,16 @@ describe("EditSponsorPage", () => { const generalTabPanel = screen.getByTestId("simple-tabpanel-0"); expect(generalTabPanel).toBeDefined(); - delete window.location; - - Object.defineProperty(window, "location", { - configurable: true, - writable: true, - value: { - ...originalWindowLocation, - hash: "#users" - } - }); + rerender( + + ); const usersTabPanel = screen.getByTestId("simple-tabpanel-1"); expect(usersTabPanel).toBeDefined(); }); - - afterEach(() => { - Object.defineProperty(window, "location", { - configurable: true, - value: originalWindowLocation - }); - }); }); }); From 82aa70957c650d852de32291b8eafc0d417cc0a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Castillo?= Date: Fri, 6 Feb 2026 19:14:16 -0300 Subject: [PATCH 5/6] fix: adjust reducer structure on test case MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Tomás Castillo --- .../sponsors/__tests__/edit-sponsor-page.test.js | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/src/pages/sponsors/__tests__/edit-sponsor-page.test.js b/src/pages/sponsors/__tests__/edit-sponsor-page.test.js index 10693623e..13845645d 100644 --- a/src/pages/sponsors/__tests__/edit-sponsor-page.test.js +++ b/src/pages/sponsors/__tests__/edit-sponsor-page.test.js @@ -81,15 +81,13 @@ describe("EditSponsorPage", () => { } }, currentSummitSponsorshipListState: { - sponsorships: { - sponsorships: [], - currentPage: 1, - lastPage: 1, - perPage: 100, - order: "order", - orderDir: 1, - totalSponsorships: 0 - } + sponsorships: [], + currentPage: 1, + lastPage: 1, + perPage: 100, + order: "order", + orderDir: 1, + totalSponsorships: 0 }, currentSponsorState: { ...currentSponsorDefaultState, From f42374c6b9744d2d306e3347057f6ea93453a442 Mon Sep 17 00:00:00 2001 From: smarcet Date: Tue, 10 Feb 2026 12:51:28 -0300 Subject: [PATCH 6/6] chore: remove dupe variable --- src/pages/sponsors/edit-sponsor-page.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/pages/sponsors/edit-sponsor-page.js b/src/pages/sponsors/edit-sponsor-page.js index c60f68fd0..4bcab5b3c 100644 --- a/src/pages/sponsors/edit-sponsor-page.js +++ b/src/pages/sponsors/edit-sponsor-page.js @@ -190,8 +190,6 @@ const EditSponsorPage = (props) => { } ]; - const sponsorFormItemRoute = !!match.params?.form_id; - return ( @@ -250,7 +248,7 @@ const EditSponsorPage = (props) => { - {sponsorFormItemRoute ? ( + {isNestedFormItemRoute ? ( ) : (