From e28b8e861dd61675312e41c168152189df8d039c Mon Sep 17 00:00:00 2001 From: Priscila Moneo Date: Fri, 6 Mar 2026 17:34:09 -0300 Subject: [PATCH] fix: bug in speakers count column in activity list --- src/actions/__tests__/event-actions.test.js | 116 ++++++++++++++++++++ src/actions/event-actions.js | 4 +- src/utils/__tests__/summitUtils.test.js | 71 ++++++++++++ src/utils/summitUtils.js | 2 +- 4 files changed, 190 insertions(+), 3 deletions(-) create mode 100644 src/actions/__tests__/event-actions.test.js create mode 100644 src/utils/__tests__/summitUtils.test.js diff --git a/src/actions/__tests__/event-actions.test.js b/src/actions/__tests__/event-actions.test.js new file mode 100644 index 000000000..0dce052d8 --- /dev/null +++ b/src/actions/__tests__/event-actions.test.js @@ -0,0 +1,116 @@ +import configureStore from "redux-mock-store"; +import thunk from "redux-thunk"; +import flushPromises from "flush-promises"; +import { getRequest } from "openstack-uicore-foundation/lib/utils/actions"; +import { getEvents } from "../event-actions"; +import * as methods from "../../utils/methods"; + +jest.mock("openstack-uicore-foundation/lib/utils/actions", () => ({ + __esModule: true, + ...jest.requireActual("openstack-uicore-foundation/lib/utils/actions"), + getRequest: jest.fn() +})); + +describe("Event Actions", () => { + const middlewares = [thunk]; + const mockStore = configureStore(middlewares); + + let capturedParams = null; + + beforeEach(() => { + jest.spyOn(methods, "getAccessTokenSafely").mockResolvedValue("TOKEN"); + getRequest.mockClear(); + + getRequest.mockImplementation( + (requestActionCreator, receiveActionCreator) => + (params = {}) => + (dispatch) => { + capturedParams = params; + + if ( + requestActionCreator && + typeof requestActionCreator === "function" + ) { + dispatch(requestActionCreator({})); + } + + return new Promise((resolve) => { + if (typeof receiveActionCreator === "function") { + dispatch(receiveActionCreator({ response: {} })); + } else { + dispatch(receiveActionCreator); + } + + resolve({ response: {} }); + }); + } + ); + }); + + afterEach(() => { + jest.restoreAllMocks(); + capturedParams = null; + }); + + test("builds speakers_count between filter using [] syntax", async () => { + const store = mockStore({ + currentSummitState: { + currentSummit: { + id: 1, + time_zone: { name: "UTC" } + } + } + }); + + store.dispatch( + getEvents(null, 1, 10, "id", 1, { speakers_count_filter: [1, 3] }, []) + ); + + await flushPromises(); + + expect(getRequest).toHaveBeenCalledTimes(1); + expect(capturedParams).toBeTruthy(); + expect(capturedParams["filter[]"]).toContain("speakers_count[]1&&3"); + expect(capturedParams["filter[]"]).not.toContain("speakers_count[]]1&&3"); + }); + + test("requests type.use_speakers in fields for event list", async () => { + const store = mockStore({ + currentSummitState: { + currentSummit: { + id: 1, + time_zone: { name: "UTC" } + } + } + }); + + store.dispatch(getEvents()); + + await flushPromises(); + + expect(getRequest).toHaveBeenCalledTimes(1); + expect(capturedParams).toBeTruthy(); + expect(capturedParams.fields).toContain("type.use_speakers"); + }); + + test("builds speakers_count operator filter when value is not an array", async () => { + const store = mockStore({ + currentSummitState: { + currentSummit: { + id: 1, + time_zone: { name: "UTC" } + } + } + }); + + store.dispatch( + getEvents(null, 1, 10, "id", 1, { speakers_count_filter: ">=2" }, []) + ); + + await flushPromises(); + + expect(getRequest).toHaveBeenCalledTimes(1); + expect(capturedParams).toBeTruthy(); + expect(capturedParams["filter[]"]).toContain("speakers_count>=2"); + }); +}); diff --git a/src/actions/event-actions.js b/src/actions/event-actions.js index d2f845a70..c1af45f33 100644 --- a/src/actions/event-actions.js +++ b/src/actions/event-actions.js @@ -261,7 +261,7 @@ const parseFilters = (filters, term = null) => { if (Array.isArray(filters.speakers_count_filter)) { // between filter.push( - `speakers_count[]]${filters.speakers_count_filter[0]}&&${filters.speakers_count_filter[1]}` + `speakers_count[]${filters.speakers_count_filter[0]}&&${filters.speakers_count_filter[1]}` ); } else { filter.push(`speakers_count${filters.speakers_count_filter}`); @@ -562,7 +562,7 @@ export const getEvents = relations: "none,speakers.none,selection_plan.none,track.none,type.none,created_by.none,location.none,media_uploads.media_upload_type.none", fields: - "id,created,last_edited,title,start_date,end_date,summit_id,duration,class_name,is_published,level,published_date,meeting_url,status,progress,selection_status,streaming_url,streaming_type,etherpad_link,location.id,location.name,speakers.id,speakers.first_name,speakers.last_name,speakers.company,track.name,track.id,created_by.first_name,created_by.last_name,created_by.email,created_by.company,selection_plan.name,selection_plan.id,media_uploads.id,media_uploads.presentation_id,media_uploads.created,media_uploads.class_name,media_uploads.display_on_site,media_uploads.media_upload_type.name,media_uploads.media_upload_type.id,type.id,type.name,sponsors.id,sponsors.name,allow_feedback,to_record,review_status", + "id,created,last_edited,title,start_date,end_date,summit_id,duration,class_name,is_published,level,published_date,meeting_url,status,progress,selection_status,streaming_url,streaming_type,etherpad_link,location.id,location.name,speakers.id,speakers.first_name,speakers.last_name,speakers.company,track.name,track.id,created_by.first_name,created_by.last_name,created_by.email,created_by.company,selection_plan.name,selection_plan.id,media_uploads.id,media_uploads.presentation_id,media_uploads.created,media_uploads.class_name,media_uploads.display_on_site,media_uploads.media_upload_type.name,media_uploads.media_upload_type.id,type.id,type.name,type.use_speakers,sponsors.id,sponsors.name,allow_feedback,to_record,review_status", page, per_page: perPage, access_token: accessToken diff --git a/src/utils/__tests__/summitUtils.test.js b/src/utils/__tests__/summitUtils.test.js new file mode 100644 index 000000000..b9416821b --- /dev/null +++ b/src/utils/__tests__/summitUtils.test.js @@ -0,0 +1,71 @@ +import { formatEventData } from "../summitUtils"; + +describe("summitUtils.formatEventData", () => { + const summit = { + time_zone: { name: "UTC" }, + time_zone_id: "UTC" + }; + + test("returns speakers_count based on speakers length when type.use_speakers is true", () => { + const event = { + id: 10, + summit_id: 1, + title: "Test Event", + is_published: false, + type: { + use_speakers: true, + allows_location: false, + allows_attendee_vote: false, + allows_publishing_dates: false + }, + speakers: [ + { first_name: "Jane", last_name: "Doe", company: "ACME" }, + { first_name: "John", last_name: "Smith", company: "Globex" } + ], + tags: [], + sponsors: [] + }; + + const result = formatEventData(event, summit); + + expect(result.speakers_count).toBe(2); + }); + + test("returns \"0\" when type.use_speakers is true and speakers list is empty", () => { + const event = { + id: 12, + summit_id: 1, + title: "No Speakers Yet", + is_published: false, + type: { + use_speakers: true, + allows_location: false, + allows_attendee_vote: false, + allows_publishing_dates: false + }, + speakers: [], + tags: [], + sponsors: [] + }; + + const result = formatEventData(event, summit); + + expect(result.speakers_count).toBe("0"); + }); + + test("returns N/A and does not throw when event type is missing", () => { + const event = { + id: 11, + summit_id: 1, + title: "Type-less Event", + is_published: false, + speakers: [], + tags: [], + sponsors: [] + }; + + expect(() => formatEventData(event, summit)).not.toThrow(); + const result = formatEventData(event, summit); + expect(result.speakers_count).toBe("N/A"); + }); +}); diff --git a/src/utils/summitUtils.js b/src/utils/summitUtils.js index c31ee8eee..525446f23 100644 --- a/src/utils/summitUtils.js +++ b/src/utils/summitUtils.js @@ -49,7 +49,7 @@ export const formatEventData = (e, summit) => { let speakers_count; - if (e.type.use_speakers) { + if (e.type?.use_speakers) { if (e.speakers && e.speakers.length > 0) { speakers_count = e.speakers.length; } else {