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
97 changes: 97 additions & 0 deletions src/actions/__tests__/room-booking-actions.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import configureStore from "redux-mock-store";
import thunk from "redux-thunk";
import flushPromises from "flush-promises";
import {
getRequest,
deleteRequest,
START_LOADING,
STOP_LOADING
} from "openstack-uicore-foundation/lib/utils/actions";
import * as methods from "../../utils/methods";
import {
getRoomBookings,
refundRoomBooking,
cancelRoomBooking,
RECEIVE_ROOM_BOOKINGS
} from "../room-booking-actions";

jest.mock("openstack-uicore-foundation/lib/utils/actions", () => ({
__esModule: true,
...jest.requireActual("openstack-uicore-foundation/lib/utils/actions"),
getRequest: jest.fn(),
deleteRequest: jest.fn()
}));

const mockStore = configureStore([thunk]);
const MOCK_STATE = { currentSummitState: { currentSummit: { id: 1 } } };

describe("room-booking-actions", () => {
beforeEach(() => {
jest.spyOn(methods, "getAccessTokenSafely").mockResolvedValue("TOKEN");
});

afterEach(() => {
jest.restoreAllMocks();
});

test("getRoomBookings drops a stale RECEIVE_ROOM_BOOKINGS when a newer call supersedes it", async () => {
let releaseStale;
let callNum = 0;
getRequest.mockImplementation(
(requestAC, receiveAC) => () => (dispatch) => {
callNum += 1;
if (callNum === 1) {
return new Promise((resolve) => {
releaseStale = () => {
dispatch(receiveAC({ response: { data: [{ id: "stale" }] } }));
resolve();
};
});
}
dispatch(receiveAC({ response: { data: [{ id: "fresh" }] } }));
return Promise.resolve();
}
);

const store = mockStore(MOCK_STATE);
const stalePromise = store.dispatch(getRoomBookings("john"));
// loader starts before the token await
expect(store.getActions().map((a) => a.type)).toEqual([START_LOADING]);
await flushPromises();
await store.dispatch(getRoomBookings("jane"));
releaseStale();
await stalePromise;

const receives = store
.getActions()
.filter((a) => a.type === RECEIVE_ROOM_BOOKINGS);
expect(receives).toHaveLength(1);
expect(receives[0].payload.response.data).toEqual([{ id: "fresh" }]);
});

test("refund and cancel reject on request failure so callers can react, still stopping the loader", async () => {
// the refund dialog stays open and the list skips its reload only if these reject
deleteRequest.mockImplementation(
() => () => () => Promise.reject(new Error("request failed"))
);

const store = mockStore(MOCK_STATE);

const refund = expect(
store.dispatch(refundRoomBooking(7, 101, 25))
).rejects.toThrow("request failed");
expect(store.getActions().map((a) => a.type)).toEqual([START_LOADING]);
await refund;

store.clearActions();
const cancel = expect(
store.dispatch(cancelRoomBooking(7, 101))
).rejects.toThrow("request failed");
expect(store.getActions().map((a) => a.type)).toEqual([START_LOADING]);
await cancel;
expect(store.getActions().map((a) => a.type)).toEqual([
START_LOADING,
STOP_LOADING
]);
});
});
43 changes: 22 additions & 21 deletions src/actions/room-booking-actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,12 @@ import {
showSuccessMessage,
getCSV,
authErrorHandler,
snackbarErrorHandler,
escapeFilterValue
} from "openstack-uicore-foundation/lib/utils/actions";
import Swal from "sweetalert2";
import history from "../history";
import { getAccessTokenSafely } from "../utils/methods";
import { getAccessTokenSafely, sequenced } from "../utils/methods";
import { DEFAULT_PER_PAGE, ERROR_CODE_412 } from "../utils/constants";

export const REQUEST_ROOM_BOOKINGS = "REQUEST_ROOM_BOOKINGS";
Expand Down Expand Up @@ -61,21 +62,19 @@ export const RECEIVE_ROOM_BOOKING_AVAILABILITY =

const normalizeEntity = (entity) => ({ ...entity });

const roomBookingsSeq = sequenced();

const parseFilters = (filters, term) => {
const filter = [];
if (term) {
const escapedTerm = escapeFilterValue(term);
filter.push(`owner_name=@${escapedTerm},room_name=@${escapedTerm}`);
}

if (filters?.email_filter) {
if (filters.email_filter.operator && filters.email_filter.value)
filter.push(
`${filters.email_filter.operator}${escapeFilterValue(
filters.email_filter.value
)}`
);
}
(filters?.email_filter ?? []).forEach(({ operator, value }) => {
if (operator && value)
filter.push(`${operator}${escapeFilterValue(value)}`);
});

return filter;
};
Expand Down Expand Up @@ -133,11 +132,13 @@ export const getRoomBookings =
filters = {}
) =>
async (dispatch, getState) => {
const { isCurrent, guardedDispatch } = roomBookingsSeq(dispatch);
const { currentSummitState } = getState();
guardedDispatch(startLoading());
Comment thread
smarcet marked this conversation as resolved.
Resolved
const accessToken = await getAccessTokenSafely();
if (!isCurrent()) return Promise.resolve();
const { currentSummit } = currentSummitState;

dispatch(startLoading());
const filter = parseFilters(filters, term);

const params = {
Expand All @@ -163,11 +164,11 @@ export const getRoomBookings =
createAction(REQUEST_ROOM_BOOKINGS),
createAction(RECEIVE_ROOM_BOOKINGS),
`${window.API_BASE_URL}/api/v1/summits/${currentSummit.id}/locations/bookable-rooms/all/reservations`,
authErrorHandler,
{ order, orderDir, term, filters }
)(params)(dispatch).then(() => {
dispatch(stopLoading());
});
snackbarErrorHandler,
{ order, orderDir, perPage, term, filters }
)(params)(guardedDispatch)
.catch(() => {})
.finally(() => guardedDispatch(stopLoading()));
};

export const exportRoomBookings =
Expand Down Expand Up @@ -282,6 +283,7 @@ export const saveRoomBooking = (entity) => async (dispatch, getState) => {

export const refundRoomBooking =
(roomId, roomBookingId, amount) => async (dispatch, getState) => {
dispatch(startLoading());
const { currentSummitState } = getState();
const accessToken = await getAccessTokenSafely();
const { currentSummit } = currentSummitState;
Expand All @@ -295,10 +297,8 @@ export const refundRoomBooking =
createAction(ROOM_BOOKING_REFUNDED),
`${window.API_BASE_URL}/api/v1/summits/${currentSummit.id}/locations/bookable-rooms/${roomId}/reservations/${roomBookingId}/refund`,
{ amount },
authErrorHandler
)(params)(dispatch).then(() => {
dispatch(stopLoading());
});
snackbarErrorHandler
)(params)(dispatch).finally(() => dispatch(stopLoading()));
};

/**
Expand All @@ -308,6 +308,7 @@ export const refundRoomBooking =
*/
export const cancelRoomBooking =
(roomId, roomBookingId) => async (dispatch, getState) => {
dispatch(startLoading());
const { currentSummitState } = getState();
const accessToken = await getAccessTokenSafely();
const { currentSummit } = currentSummitState;
Expand All @@ -321,8 +322,8 @@ export const cancelRoomBooking =
createAction(ROOM_BOOKING_DELETED),
`${window.API_BASE_URL}/api/v1/summits/${currentSummit.id}/locations/bookable-rooms/${roomId}/reservations/${roomBookingId}/cancel`,
{},
authErrorHandler
)(params)(dispatch).then(() => dispatch(stopLoading()));
snackbarErrorHandler
)(params)(dispatch).finally(() => dispatch(stopLoading()));
};

/** ******************** ATTRIBUTE TYPE **************************************************************** */
Expand Down
84 changes: 0 additions & 84 deletions src/components/inputs/email-filter/index.js

This file was deleted.

4 changes: 0 additions & 4 deletions src/components/inputs/email-filter/index.module.less

This file was deleted.

12 changes: 5 additions & 7 deletions src/i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -1615,9 +1615,11 @@
"amount": "Amount",
"paid": "Paid",
"refunded": "Refunded",
"delete_booking_warning": "Are you sure you want to delete booking for ",
"delete_booking_warning": "Are you sure you want to delete booking for {name}",
"add_room_booking": "Add Offline Room Booking",
"apply_filters": "Apply Filters",
"refund_title": "Refund {owner_name} for room {room_name}",
"user_paid": "User Paid",
"refund_amount": "Refund Amount",
"placeholders": {
"search_bookings": "Search bookings."
}
Expand Down Expand Up @@ -3998,12 +4000,8 @@
"reg_feed_metadata_created": "Registration Feed Metadata created successfully."
},
"email_filter": {
"operator_contains": "Contains",
"operator_not_contains": "Not Contains",
"operator_equal": "Equal",
"placeholders": {
"value": "Search by email address",
"operator": "Select Operator"
"value": "Search by email address"
}
},
"notes": {
Expand Down
Loading
Loading