Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@
"moment-duration-format": "^2.3.2",
"moment-timezone": "^0.5.33",
"mui-color-input": "^9.0.0",
"openstack-uicore-foundation": "5.0.38",
"openstack-uicore-foundation": "5.0.39",
"p-limit": "^6.1.0",
"path-browserify": "^1.0.1",
"postcss-loader": "^6.2.1",
Expand All @@ -115,7 +115,7 @@
"react-dropzone": "^4.2.13",
"react-final-form": "^6.5.9",
"react-google-maps": "^9.4.5",
"react-redux": "^5.0.7",
"react-redux": "^7.1.0",
"react-router": "^4.3.1",
"react-router-dom": "^4.3.1",
"react-rte": "^0.16.3",
Expand All @@ -125,7 +125,7 @@
"react-switch": "^6.0.0",
"react-tooltip": "^5.28.0",
"react-window": "^1.8.10",
"redux": "^3.7.2",
"redux": "^4.2.1",
"redux-persist": "^5.10.0",
"redux-thunk": "^2.3.0",
"segmented-control": "0.1.12",
Expand Down
139 changes: 129 additions & 10 deletions src/actions/__tests__/event-actions.test.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,22 @@
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 {
getRequest,
postFile,
getRawCSV,
downloadFileByContent
} from "openstack-uicore-foundation/lib/utils/actions";
import { getEvents, exportEvents, importEventsCSV } 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()
getRequest: jest.fn(),
postFile: jest.fn(),
getRawCSV: jest.fn(),
downloadFileByContent: jest.fn()
}));

describe("Event Actions", () => {
Expand Down Expand Up @@ -52,7 +60,7 @@ describe("Event Actions", () => {
capturedParams = null;
});

test("builds speakers_count between filter using [] syntax", async () => {
test("passes pre-built [] range filter strings through to the request", async () => {
const store = mockStore({
currentSummitState: {
currentSummit: {
Expand All @@ -63,15 +71,14 @@ describe("Event Actions", () => {
});

store.dispatch(
getEvents(null, 1, 10, "id", 1, { speakers_count_filter: [1, 3] }, [])
getEvents(null, 1, 10, "id", 1, ["speakers_count[]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 () => {
Expand All @@ -93,7 +100,7 @@ describe("Event Actions", () => {
expect(capturedParams.fields).toContain("type.use_speakers");
});

test("builds speakers_count operator filter when value is not an array", async () => {
test("passes pre-built operator filter strings through to the request", async () => {
const store = mockStore({
currentSummitState: {
currentSummit: {
Expand All @@ -103,14 +110,126 @@ describe("Event Actions", () => {
}
});

store.dispatch(
getEvents(null, 1, 10, "id", 1, { speakers_count_filter: ">=2" }, [])
);
store.dispatch(getEvents(null, 1, 10, "id", 1, ["speakers_count>=2"], []));

await flushPromises();

expect(getRequest).toHaveBeenCalledTimes(1);
expect(capturedParams).toBeTruthy();
expect(capturedParams["filter[]"]).toContain("speakers_count>=2");
});

describe("importEventsCSV", () => {
beforeEach(() => {
postFile.mockReset();
});

test("resolves and reloads the page after a successful upload", async () => {
postFile.mockImplementation(
(start, successActionCreator) => () => (dispatch) => {
dispatch(successActionCreator({ response: {} }));
return Promise.resolve({ response: {} });
}
);

const store = mockStore({
currentSummitState: { currentSummit: { id: 1 } }
});
const file = new File(["a,b"], "events.csv");

await store.dispatch(importEventsCSV(file, true));

expect(postFile).toHaveBeenCalledTimes(1);
const [, , , calledFile, extraFields] = postFile.mock.calls[0];
expect(calledFile).toBe(file);
expect(extraFields).toEqual({ send_speaker_email: true });
// jsdom's window.location.reload can't be mocked (non-configurable),
// so we verify the success path ran via the other dispatched action
// instead of the reload call itself.
const actions = store.getActions();
expect(actions.some((a) => a.type === "STOP_LOADING")).toBe(true);
});

test("propagates a rejection when the upload fails, so the caller can catch it", async () => {
const uploadError = new Error("upload failed");
postFile.mockImplementation(
() => () => () => Promise.reject(uploadError)
);

const store = mockStore({
currentSummitState: { currentSummit: { id: 1 } }
});

await expect(
store.dispatch(importEventsCSV(new File(["a"], "events.csv"), false))
).rejects.toBe(uploadError);
});
});

describe("exportEvents", () => {
beforeEach(() => {
getRawCSV.mockReset();
downloadFileByContent.mockReset();
getRawCSV.mockResolvedValue("id,title\n1,Test Event");
});

test("fetches a single CSV page and downloads it when results fit within one page", async () => {
const store = mockStore({
currentSummitState: { currentSummit: { id: 1, name: "MySummit" } },
currentEventListState: { totalEvents: 50 }
});

store.dispatch(exportEvents());
await flushPromises();

expect(getRawCSV).toHaveBeenCalledTimes(1);
expect(downloadFileByContent).toHaveBeenCalledTimes(1);
const [filename] = downloadFileByContent.mock.calls[0];
expect(filename).toBe("MySummit-Activities.csv");
});

test("fetches one CSV page per chunk of EXPORT_PAGE_SIZE_200 events", async () => {
const store = mockStore({
currentSummitState: { currentSummit: { id: 1, name: "MySummit" } },
currentEventListState: { totalEvents: 450 }
});

store.dispatch(exportEvents());
await flushPromises();

// 450 events / 200 per page => 3 pages
expect(getRawCSV).toHaveBeenCalledTimes(3);
});

test("adds an escaped search filter across title/abstract/speaker_title when a term is provided", async () => {
const store = mockStore({
currentSummitState: { currentSummit: { id: 1, name: "MySummit" } },
currentEventListState: { totalEvents: 10 }
});

store.dispatch(exportEvents("hello"));
await flushPromises();

const [, params] = getRawCSV.mock.calls[0];
expect(params["filter[]"]).toEqual(
expect.arrayContaining([expect.stringContaining("title=@hello")])
);
});

test("still stops loading when the CSV fetch fails", async () => {
getRawCSV.mockRejectedValue(new Error("network error"));

const store = mockStore({
currentSummitState: { currentSummit: { id: 1, name: "MySummit" } },
currentEventListState: { totalEvents: 10 }
});

store.dispatch(exportEvents());
await flushPromises();

expect(downloadFileByContent).not.toHaveBeenCalled();
const actions = store.getActions();
expect(actions.some((a) => a.type === "STOP_LOADING")).toBe(true);
});
});
});
Loading
Loading