From 7ac284bc64adbeabd13ad51af9c87987c43c9845 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Castillo?= Date: Tue, 26 May 2026 22:45:00 -0300 Subject: [PATCH 1/4] fix: adjust payload to customized managed sponsor forms MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Tomás Castillo --- .../sponsors/sponsor-customized-form-reducer.js | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/reducers/sponsors/sponsor-customized-form-reducer.js b/src/reducers/sponsors/sponsor-customized-form-reducer.js index 9a2d22a36..3daaca959 100644 --- a/src/reducers/sponsors/sponsor-customized-form-reducer.js +++ b/src/reducers/sponsors/sponsor-customized-form-reducer.js @@ -43,10 +43,14 @@ const sponsorCustomizedFormReducer = (state = DEFAULT_STATE, action) => { return DEFAULT_STATE; } case RECEIVE_SPONSOR_CUSTOMIZED_FORM: { - return { - ...state, - entity: payload.response + const entity = { + ...payload.response, + items: payload.response.items.map((it) => ({ + ...it, + images: it.images.map((img) => ({ ...img, file_path: img.file_url })) + })) }; + return { ...state, entity }; } default: return state; From ff59678ec0d1b7102a109c653ff1a83174e93ef3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Castillo?= Date: Tue, 26 May 2026 22:51:57 -0300 Subject: [PATCH 2/4] fix: add safety guards for items and arrays MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Tomás Castillo --- src/reducers/sponsors/sponsor-customized-form-reducer.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/reducers/sponsors/sponsor-customized-form-reducer.js b/src/reducers/sponsors/sponsor-customized-form-reducer.js index 3daaca959..7d4cdcc38 100644 --- a/src/reducers/sponsors/sponsor-customized-form-reducer.js +++ b/src/reducers/sponsors/sponsor-customized-form-reducer.js @@ -45,9 +45,12 @@ const sponsorCustomizedFormReducer = (state = DEFAULT_STATE, action) => { case RECEIVE_SPONSOR_CUSTOMIZED_FORM: { const entity = { ...payload.response, - items: payload.response.items.map((it) => ({ + items: (payload.response.items || []).map((it) => ({ ...it, - images: it.images.map((img) => ({ ...img, file_path: img.file_url })) + images: (it.images || []).map((img) => ({ + ...img, + file_path: img.file_url + })) })) }; return { ...state, entity }; From e7daa566c9f358451ebcbae32412ad1d297d18a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Castillo?= Date: Thu, 28 May 2026 01:35:37 -0300 Subject: [PATCH 3/4] fix: add items to default entity, unit test, map images on customized form item reducer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Tomás Castillo --- .../sponsor-customized-form-reducer.test.js | 105 ++++++++++++++++++ ...nsor-customized-form-items-list-reducer.js | 4 + .../sponsor-customized-form-reducer.js | 3 +- 3 files changed, 111 insertions(+), 1 deletion(-) create mode 100644 src/reducers/sponsors/__tests__/sponsor-customized-form-reducer.test.js diff --git a/src/reducers/sponsors/__tests__/sponsor-customized-form-reducer.test.js b/src/reducers/sponsors/__tests__/sponsor-customized-form-reducer.test.js new file mode 100644 index 000000000..10d4102e1 --- /dev/null +++ b/src/reducers/sponsors/__tests__/sponsor-customized-form-reducer.test.js @@ -0,0 +1,105 @@ +import sponsorCustomizedFormReducer from "../sponsor-customized-form-reducer"; +import { + RECEIVE_SPONSOR_CUSTOMIZED_FORM, + RESET_SPONSOR_CUSTOMIZED_FORM +} from "../../../actions/sponsor-forms-actions"; + +const DEFAULT_ENTITY = { + id: 0, + code: "", + name: "", + allowed_add_ons: [], + opens_at: "", + expires_at: "", + instructions: "", + meta_fields: [], + items: [] +}; + +const DEFAULT_STATE = { entity: DEFAULT_ENTITY }; + +describe("sponsorCustomizedFormReducer", () => { + describe("RECEIVE_SPONSOR_CUSTOMIZED_FORM", () => { + it("maps file_url to file_path for images on each item", () => { + const result = sponsorCustomizedFormReducer(DEFAULT_STATE, { + type: RECEIVE_SPONSOR_CUSTOMIZED_FORM, + payload: { + response: { + id: 1, + code: "FORM1", + items: [ + { + id: 10, + name: "Item A", + images: [ + { id: 100, file_url: "https://example.com/img1.png" }, + { id: 101, file_url: "https://example.com/img2.png" } + ] + } + ] + } + } + }); + + expect(result.entity.items[0].images).toEqual([ + { + id: 100, + file_url: "https://example.com/img1.png", + file_path: "https://example.com/img1.png" + }, + { + id: 101, + file_url: "https://example.com/img2.png", + file_path: "https://example.com/img2.png" + } + ]); + }); + + it("handles items with no images", () => { + const result = sponsorCustomizedFormReducer(DEFAULT_STATE, { + type: RECEIVE_SPONSOR_CUSTOMIZED_FORM, + payload: { + response: { + id: 1, + code: "FORM1", + items: [{ id: 10, name: "Item A", images: [] }] + } + } + }); + + expect(result.entity.items[0].images).toEqual([]); + }); + + it("handles empty items array", () => { + const result = sponsorCustomizedFormReducer(DEFAULT_STATE, { + type: RECEIVE_SPONSOR_CUSTOMIZED_FORM, + payload: { + response: { id: 1, code: "FORM1", items: [] } + } + }); + + expect(result.entity.items).toEqual([]); + }); + + it("handles missing items field", () => { + const result = sponsorCustomizedFormReducer(DEFAULT_STATE, { + type: RECEIVE_SPONSOR_CUSTOMIZED_FORM, + payload: { + response: { id: 1, code: "FORM1" } + } + }); + + expect(result.entity.items).toEqual([]); + }); + }); + + describe("RESET_SPONSOR_CUSTOMIZED_FORM", () => { + it("resets to default state", () => { + const dirty = { entity: { id: 99, items: [{ id: 1 }] } }; + const result = sponsorCustomizedFormReducer(dirty, { + type: RESET_SPONSOR_CUSTOMIZED_FORM + }); + expect(result).toEqual(DEFAULT_STATE); + }); + }); +}); diff --git a/src/reducers/sponsors/sponsor-customized-form-items-list-reducer.js b/src/reducers/sponsors/sponsor-customized-form-items-list-reducer.js index c181d463a..5ca54c7d7 100644 --- a/src/reducers/sponsors/sponsor-customized-form-items-list-reducer.js +++ b/src/reducers/sponsors/sponsor-customized-form-items-list-reducer.js @@ -110,6 +110,10 @@ const sponsorCustomizedFormItemsListReducer = ( const currentItem = { ...item, + images: (item.images || []).map((img) => ({ + ...img, + file_path: img.file_url + })), meta_fields: item.meta_fields.length > 0 ? item.meta_fields : [] }; return { ...state, currentItem }; diff --git a/src/reducers/sponsors/sponsor-customized-form-reducer.js b/src/reducers/sponsors/sponsor-customized-form-reducer.js index 7d4cdcc38..807ddad98 100644 --- a/src/reducers/sponsors/sponsor-customized-form-reducer.js +++ b/src/reducers/sponsors/sponsor-customized-form-reducer.js @@ -26,7 +26,8 @@ const DEFAULT_ENTITY = { opens_at: "", expires_at: "", instructions: "", - meta_fields: [] + meta_fields: [], + items: [] }; const DEFAULT_STATE = { From d212b8914ae1b1644e654b472c11692763d6052d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Castillo?= Date: Tue, 9 Jun 2026 17:36:36 -0300 Subject: [PATCH 4/4] fix: add unit test cases, adjust reducer and add context comment on shared actions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Tomás Castillo --- ...customized-form-items-list-reducer.test.js | 125 ++++++++++++++++++ ...nsor-customized-form-items-list-reducer.js | 2 +- .../sponsor-customized-form-reducer.js | 5 + 3 files changed, 131 insertions(+), 1 deletion(-) create mode 100644 src/reducers/sponsors/__tests__/sponsor-customized-form-items-list-reducer.test.js diff --git a/src/reducers/sponsors/__tests__/sponsor-customized-form-items-list-reducer.test.js b/src/reducers/sponsors/__tests__/sponsor-customized-form-items-list-reducer.test.js new file mode 100644 index 000000000..23ac56ae5 --- /dev/null +++ b/src/reducers/sponsors/__tests__/sponsor-customized-form-items-list-reducer.test.js @@ -0,0 +1,125 @@ +import sponsorCustomizedFormItemsListReducer from "../sponsor-customized-form-items-list-reducer"; +import { + RECEIVE_SPONSOR_CUSTOMIZED_FORM_ITEM, + SPONSOR_FORM_MANAGED_ITEM_UPDATED +} from "../../../actions/sponsor-forms-actions"; + +const DEFAULT_STATE = { + items: [], + showArchived: false, + term: "", + order: "name", + orderDir: 1, + currentPage: 1, + lastPage: 1, + perPage: 10, + totalCount: 0, + currentItem: { + code: "", + name: "", + description: "", + early_bird_rate: 0, + standard_rate: 0, + onsite_rate: 0, + quantity_limit_per_show: 0, + quantity_limit_per_sponsor: 0, + default_quantity: 0, + images: [], + meta_fields: [] + } +}; + +const buildItem = (overrides = {}) => ({ + id: 1, + code: "ITEM1", + name: "Item One", + description: "desc", + early_bird_rate: 100, + standard_rate: 200, + onsite_rate: 300, + default_quantity: 5, + is_archived: false, + images: [], + meta_fields: [], + ...overrides +}); + +describe("sponsorCustomizedFormItemsListReducer", () => { + describe("RECEIVE_SPONSOR_CUSTOMIZED_FORM_ITEM", () => { + it("maps file_url to file_path on each image — the edit-form image fix", () => { + const result = sponsorCustomizedFormItemsListReducer(DEFAULT_STATE, { + type: RECEIVE_SPONSOR_CUSTOMIZED_FORM_ITEM, + payload: { + response: buildItem({ + images: [ + { id: 10, file_url: "https://cdn/a.png" }, + { id: 11, file_url: "https://cdn/b.png" } + ] + }) + } + }); + + expect(result.currentItem.images).toEqual([ + { + id: 10, + file_url: "https://cdn/a.png", + file_path: "https://cdn/a.png" + }, + { + id: 11, + file_url: "https://cdn/b.png", + file_path: "https://cdn/b.png" + } + ]); + }); + + it("handles absent images without throwing", () => { + const result = sponsorCustomizedFormItemsListReducer(DEFAULT_STATE, { + type: RECEIVE_SPONSOR_CUSTOMIZED_FORM_ITEM, + payload: { response: buildItem({ images: undefined }) } + }); + + expect(result.currentItem.images).toEqual([]); + }); + + it("handles absent meta_fields without throwing — guards the .length access", () => { + const result = sponsorCustomizedFormItemsListReducer(DEFAULT_STATE, { + type: RECEIVE_SPONSOR_CUSTOMIZED_FORM_ITEM, + payload: { response: buildItem({ meta_fields: undefined }) } + }); + + expect(result.currentItem.meta_fields).toEqual([]); + }); + }); + + describe("SPONSOR_FORM_MANAGED_ITEM_UPDATED", () => { + it("replaces the matching list item and preserves its images as-is", () => { + const images = [{ id: 20, file_url: "https://cdn/img.png" }]; + const state = { + ...DEFAULT_STATE, + items: [ + buildItem({ id: 1, name: "Before", images }), + buildItem({ id: 2, name: "Other" }) + ] + }; + + const result = sponsorCustomizedFormItemsListReducer(state, { + type: SPONSOR_FORM_MANAGED_ITEM_UPDATED, + payload: { + response: buildItem({ + id: 1, + name: "After", + early_bird_rate: 500, + standard_rate: 600, + onsite_rate: 700, + images + }) + } + }); + + expect(result.items[0].name).toBe("After"); + expect(result.items[0].images).toBe(images); + expect(result.items[1].name).toBe("Other"); + }); + }); +}); diff --git a/src/reducers/sponsors/sponsor-customized-form-items-list-reducer.js b/src/reducers/sponsors/sponsor-customized-form-items-list-reducer.js index 5ca54c7d7..a2de1b315 100644 --- a/src/reducers/sponsors/sponsor-customized-form-items-list-reducer.js +++ b/src/reducers/sponsors/sponsor-customized-form-items-list-reducer.js @@ -114,7 +114,7 @@ const sponsorCustomizedFormItemsListReducer = ( ...img, file_path: img.file_url })), - meta_fields: item.meta_fields.length > 0 ? item.meta_fields : [] + meta_fields: (item.meta_fields ?? []).length > 0 ? item.meta_fields : [] }; return { ...state, currentItem }; } diff --git a/src/reducers/sponsors/sponsor-customized-form-reducer.js b/src/reducers/sponsors/sponsor-customized-form-reducer.js index 807ddad98..9eb224544 100644 --- a/src/reducers/sponsors/sponsor-customized-form-reducer.js +++ b/src/reducers/sponsors/sponsor-customized-form-reducer.js @@ -44,6 +44,11 @@ const sponsorCustomizedFormReducer = (state = DEFAULT_STATE, action) => { return DEFAULT_STATE; } case RECEIVE_SPONSOR_CUSTOMIZED_FORM: { + // this actions is dispatched by getSponsorManagedForm and getSponsorCustomizedForm + // getSponsorManagedForm expands items.images, getSponsorCustomizedForm not, + // so items is absent here for the customized path. + // Add expand=items,items.images to that action if item display is ever needed in + // the customized-form popup. const entity = { ...payload.response, items: (payload.response.items || []).map((it) => ({