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/__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..a2de1b315 100644 --- a/src/reducers/sponsors/sponsor-customized-form-items-list-reducer.js +++ b/src/reducers/sponsors/sponsor-customized-form-items-list-reducer.js @@ -110,7 +110,11 @@ const sponsorCustomizedFormItemsListReducer = ( const currentItem = { ...item, - meta_fields: item.meta_fields.length > 0 ? item.meta_fields : [] + 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 9a2d22a36..9eb224544 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 = { @@ -43,10 +44,22 @@ const sponsorCustomizedFormReducer = (state = DEFAULT_STATE, action) => { return DEFAULT_STATE; } case RECEIVE_SPONSOR_CUSTOMIZED_FORM: { - return { - ...state, - entity: payload.response + // 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) => ({ + ...it, + images: (it.images || []).map((img) => ({ + ...img, + file_path: img.file_url + })) + })) }; + return { ...state, entity }; } default: return state;