diff --git a/src/actions/__tests__/form-template-actions.test.js b/src/actions/__tests__/form-template-actions.test.js new file mode 100644 index 000000000..d0c3ea4b0 --- /dev/null +++ b/src/actions/__tests__/form-template-actions.test.js @@ -0,0 +1,82 @@ +import { createStore, combineReducers, applyMiddleware } from "redux"; +import thunk from "redux-thunk"; +import flushPromises from "flush-promises"; +import { + putRequest, + getRequest +} from "openstack-uicore-foundation/lib/utils/actions"; +import { + archiveFormTemplate, + RECEIVE_FORM_TEMPLATES +} from "../form-template-actions"; +import formTemplateListReducer from "../../reducers/sponsors_inventory/form-template-list-reducer"; +import * as methods from "../../utils/methods"; + +jest.mock("openstack-uicore-foundation/lib/utils/actions", () => ({ + __esModule: true, + ...jest.requireActual("openstack-uicore-foundation/lib/utils/actions"), + putRequest: jest.fn(), + getRequest: jest.fn() +})); + +describe("archiveFormTemplate", () => { + beforeEach(() => { + window.INVENTORY_API_BASE_URL = "http://test-api"; + jest.spyOn(methods, "getAccessTokenSafely").mockReturnValue("TOKEN"); + + putRequest.mockImplementation( + (nullArg, receiveActionCreator) => () => (dispatch) => { + dispatch( + receiveActionCreator({ response: { id: 21, is_archived: true } }) + ); + return Promise.resolve({ response: { id: 21, is_archived: true } }); + } + ); + + getRequest.mockImplementation( + () => () => () => Promise.resolve({ response: {} }) + ); + }); + + afterEach(() => { + jest.restoreAllMocks(); + }); + + it("refetches the corrected page after archiving the last item on the last page", async () => { + // Real store so the reducer runs and getState() reflects the corrected page + const store = createStore( + combineReducers({ + currentFormTemplateListState: formTemplateListReducer + }), + applyMiddleware(thunk) + ); + + // Land on page 3 with 21 items (1 item on page 3, 10 per page) + store.dispatch({ + type: RECEIVE_FORM_TEMPLATES, + payload: { + response: { + data: [{ id: 21, is_archived: false, items: [] }], + total: 21, + last_page: 3, + current_page: 3 + } + } + }); + + await store.dispatch(archiveFormTemplate({ id: 21 })); + await flushPromises(); + + // Reducer corrects page 3 → 2 after FORM_TEMPLATE_ARCHIVED + expect(store.getState().currentFormTemplateListState.currentPage).toBe(2); + + // getFormTemplates must refetch page 2, not the stale page 3 + expect(getRequest).toHaveBeenCalledWith( + expect.any(Function), + expect.any(Function), + "http://test-api/api/v1/form-templates", + expect.any(Function), + expect.objectContaining({ page: 2 }) + ); + }); +}); diff --git a/src/actions/form-template-actions.js b/src/actions/form-template-actions.js index dfa330bdd..8cc700481 100644 --- a/src/actions/form-template-actions.js +++ b/src/actions/form-template-actions.js @@ -325,18 +325,44 @@ export const deleteFormTemplateMaterial = (templateId, materialId) => { /* ************************************** ARCHIVE ************************************** */ -export const archiveFormTemplate = (formTemplate) => { - const settings = { - url: `${window.INVENTORY_API_BASE_URL}/api/v1/form-templates/${formTemplate.id}/archive`, - updatedActionName: FORM_TEMPLATE_ARCHIVED +export const archiveFormTemplate = + (formTemplate) => async (dispatch, getState) => { + const settings = { + url: `${window.INVENTORY_API_BASE_URL}/api/v1/form-templates/${formTemplate.id}/archive`, + updatedActionName: FORM_TEMPLATE_ARCHIVED + }; + await archiveItem(formTemplate, settings)(dispatch); + const { term, currentPage, perPage, order, orderDir, showArchived } = + getState().currentFormTemplateListState; + dispatch( + getFormTemplates( + term, + currentPage, + perPage, + order, + orderDir, + showArchived + ) + ); }; - return archiveItem(formTemplate, settings); -}; -export const unarchiveFormTemplate = (formTemplate) => { - const settings = { - url: `${window.INVENTORY_API_BASE_URL}/api/v1/form-templates/${formTemplate.id}/archive`, - deletedActionName: FORM_TEMPLATE_UNARCHIVED +export const unarchiveFormTemplate = + (formTemplate) => async (dispatch, getState) => { + const settings = { + url: `${window.INVENTORY_API_BASE_URL}/api/v1/form-templates/${formTemplate.id}/archive`, + deletedActionName: FORM_TEMPLATE_UNARCHIVED + }; + await unarchiveItem(formTemplate, settings)(dispatch); + const { term, currentPage, perPage, order, orderDir, showArchived } = + getState().currentFormTemplateListState; + dispatch( + getFormTemplates( + term, + currentPage, + perPage, + order, + orderDir, + showArchived + ) + ); }; - return unarchiveItem(formTemplate, settings); -}; diff --git a/src/actions/form-template-item-actions.js b/src/actions/form-template-item-actions.js index 53086a433..f1de074ab 100644 --- a/src/actions/form-template-item-actions.js +++ b/src/actions/form-template-item-actions.js @@ -393,18 +393,46 @@ export const deleteItemImage = ( /* ************************************** ARCHIVE ************************************** */ -export const archiveFormTemplateItem = (formTemplateId, formTemplateItem) => { - const settings = { - url: `${window.INVENTORY_API_BASE_URL}/api/v1/form-templates/${formTemplateId}/items/${formTemplateItem.id}/archive`, - updatedActionName: FORM_TEMPLATE_ITEM_ARCHIVED +export const archiveFormTemplateItem = + (formTemplateId, formTemplateItem) => async (dispatch, getState) => { + const settings = { + url: `${window.INVENTORY_API_BASE_URL}/api/v1/form-templates/${formTemplateId}/items/${formTemplateItem.id}/archive`, + updatedActionName: FORM_TEMPLATE_ITEM_ARCHIVED + }; + await archiveItem(formTemplateItem, settings)(dispatch); + const { term, currentPage, perPage, order, orderDir, showArchived } = + getState().currentFormTemplateItemListState; + dispatch( + getFormTemplateItems( + formTemplateId, + term, + currentPage, + perPage, + order, + orderDir, + showArchived + ) + ); }; - return archiveItem(formTemplateItem, settings); -}; -export const unarchiveFormTemplateItem = (formTemplateId, formTemplateItem) => { - const settings = { - url: `${window.INVENTORY_API_BASE_URL}/api/v1/form-templates/${formTemplateId}/items/${formTemplateItem.id}/archive`, - deletedActionName: FORM_TEMPLATE_ITEM_UNARCHIVED +export const unarchiveFormTemplateItem = + (formTemplateId, formTemplateItem) => async (dispatch, getState) => { + const settings = { + url: `${window.INVENTORY_API_BASE_URL}/api/v1/form-templates/${formTemplateId}/items/${formTemplateItem.id}/archive`, + deletedActionName: FORM_TEMPLATE_ITEM_UNARCHIVED + }; + await unarchiveItem(formTemplateItem, settings)(dispatch); + const { term, currentPage, perPage, order, orderDir, showArchived } = + getState().currentFormTemplateItemListState; + dispatch( + getFormTemplateItems( + formTemplateId, + term, + currentPage, + perPage, + order, + orderDir, + showArchived + ) + ); }; - return unarchiveItem(formTemplateItem, settings); -}; diff --git a/src/actions/inventory-item-actions.js b/src/actions/inventory-item-actions.js index 1b6277390..b60dc7de0 100644 --- a/src/actions/inventory-item-actions.js +++ b/src/actions/inventory-item-actions.js @@ -370,18 +370,44 @@ export const deleteInventoryItemImage = (inventoryItemId, imageId) => { /* ************************************** ARCHIVE ************************************** */ -export const archiveInventoryItem = (inventoryItem) => { - const settings = { - url: `${window.INVENTORY_API_BASE_URL}/api/v1/inventory-items/${inventoryItem.id}/archive`, - updatedActionName: INVENTORY_ITEM_ARCHIVED +export const archiveInventoryItem = + (inventoryItem) => async (dispatch, getState) => { + const settings = { + url: `${window.INVENTORY_API_BASE_URL}/api/v1/inventory-items/${inventoryItem.id}/archive`, + updatedActionName: INVENTORY_ITEM_ARCHIVED + }; + await archiveItem(inventoryItem, settings)(dispatch); + const { term, currentPage, perPage, order, orderDir, showArchived } = + getState().currentInventoryItemListState; + dispatch( + getInventoryItems( + term, + currentPage, + perPage, + order, + orderDir, + showArchived + ) + ); }; - return archiveItem(inventoryItem, settings); -}; -export const unarchiveInventoryItem = (inventoryItem) => { - const settings = { - url: `${window.INVENTORY_API_BASE_URL}/api/v1/inventory-items/${inventoryItem.id}/archive`, - deletedActionName: INVENTORY_ITEM_UNARCHIVED +export const unarchiveInventoryItem = + (inventoryItem) => async (dispatch, getState) => { + const settings = { + url: `${window.INVENTORY_API_BASE_URL}/api/v1/inventory-items/${inventoryItem.id}/archive`, + deletedActionName: INVENTORY_ITEM_UNARCHIVED + }; + await unarchiveItem(inventoryItem, settings)(dispatch); + const { term, currentPage, perPage, order, orderDir, showArchived } = + getState().currentInventoryItemListState; + dispatch( + getInventoryItems( + term, + currentPage, + perPage, + order, + orderDir, + showArchived + ) + ); }; - return unarchiveItem(inventoryItem, settings); -}; diff --git a/src/actions/page-template-actions.js b/src/actions/page-template-actions.js index ecf7ca78b..62c631d7d 100644 --- a/src/actions/page-template-actions.js +++ b/src/actions/page-template-actions.js @@ -211,35 +211,60 @@ export const savePageTemplate = (entity) => async (dispatch) => { /* ************************************** ARCHIVE ************************************** */ -export const archivePageTemplate = (pageTemplateId) => async (dispatch) => { - const accessToken = await getAccessTokenSafely(); - const params = { access_token: accessToken }; - - return putRequest( - null, - createAction(PAGE_TEMPLATE_ARCHIVED), - `${window.SPONSOR_PAGES_API_URL}/api/v1/page-templates/${pageTemplateId}/archive`, - null, - snackbarErrorHandler - )(params)(dispatch); -}; +export const archivePageTemplate = + (pageTemplateId) => async (dispatch, getState) => { + const accessToken = await getAccessTokenSafely(); + const params = { access_token: accessToken }; + + await putRequest( + null, + createAction(PAGE_TEMPLATE_ARCHIVED), + `${window.SPONSOR_PAGES_API_URL}/api/v1/page-templates/${pageTemplateId}/archive`, + null, + snackbarErrorHandler + )(params)(dispatch); + const { term, currentPage, perPage, order, orderDir, showArchived } = + getState().pageTemplateListState; + dispatch( + getPageTemplates( + term, + currentPage, + perPage, + order, + orderDir, + showArchived + ) + ); + }; -export const unarchivePageTemplate = (pageTemplateId) => async (dispatch) => { - const accessToken = await getAccessTokenSafely(); - const params = { access_token: accessToken }; +export const unarchivePageTemplate = + (pageTemplateId) => async (dispatch, getState) => { + const accessToken = await getAccessTokenSafely(); + const params = { access_token: accessToken }; - dispatch(startLoading()); + dispatch(startLoading()); - return deleteRequest( - null, - createAction(PAGE_TEMPLATE_UNARCHIVED)({ pageTemplateId }), - `${window.SPONSOR_PAGES_API_URL}/api/v1/page-templates/${pageTemplateId}/archive`, - null, - snackbarErrorHandler - )(params)(dispatch).then(() => { + await deleteRequest( + null, + createAction(PAGE_TEMPLATE_UNARCHIVED)({ pageTemplateId }), + `${window.SPONSOR_PAGES_API_URL}/api/v1/page-templates/${pageTemplateId}/archive`, + null, + snackbarErrorHandler + )(params)(dispatch); dispatch(stopLoading()); - }); -}; + const { term, currentPage, perPage, order, orderDir, showArchived } = + getState().pageTemplateListState; + dispatch( + getPageTemplates( + term, + currentPage, + perPage, + order, + orderDir, + showArchived + ) + ); + }; export const clonePageTemplate = (templateId) => async (dispatch) => { const accessToken = await getAccessTokenSafely(); diff --git a/src/actions/show-pages-actions.js b/src/actions/show-pages-actions.js index 42bf426b1..56034be7c 100644 --- a/src/actions/show-pages-actions.js +++ b/src/actions/show-pages-actions.js @@ -236,24 +236,25 @@ export const archiveShowPage = (pageId) => async (dispatch, getState) => { dispatch(startLoading()); - return putRequest( + await putRequest( null, createAction(SHOW_PAGE_ARCHIVED)({ pageId }), `${window.SPONSOR_PAGES_API_URL}/api/v1/summits/${currentSummit.id}/show-pages/${pageId}/archive`, null, snackbarErrorHandler - )(params)(dispatch) - .then(() => { - dispatch( - snackbarSuccessHandler({ - title: T.translate("general.success"), - html: T.translate("show_pages.archived") - }) - ); + )(params)(dispatch); + dispatch( + snackbarSuccessHandler({ + title: T.translate("general.success"), + html: T.translate("show_pages.archived") }) - .finally(() => { - dispatch(stopLoading()); - }); + ); + dispatch(stopLoading()); + const { term, currentPage, perPage, order, orderDir, showArchived } = + getState().showPagesListState; + dispatch( + getShowPages(term, currentPage, perPage, order, orderDir, showArchived) + ); }; export const unarchiveShowPage = (pageId) => async (dispatch, getState) => { @@ -264,22 +265,23 @@ export const unarchiveShowPage = (pageId) => async (dispatch, getState) => { dispatch(startLoading()); - return deleteRequest( + await deleteRequest( null, createAction(SHOW_PAGE_UNARCHIVED)({ pageId }), `${window.SPONSOR_PAGES_API_URL}/api/v1/summits/${currentSummit.id}/show-pages/${pageId}/archive`, null, snackbarErrorHandler - )(params)(dispatch) - .then(() => { - dispatch( - snackbarSuccessHandler({ - title: T.translate("general.success"), - html: T.translate("show_pages.unarchived") - }) - ); + )(params)(dispatch); + dispatch( + snackbarSuccessHandler({ + title: T.translate("general.success"), + html: T.translate("show_pages.unarchived") }) - .finally(() => { - dispatch(stopLoading()); - }); + ); + dispatch(stopLoading()); + const { term, currentPage, perPage, order, orderDir, showArchived } = + getState().showPagesListState; + dispatch( + getShowPages(term, currentPage, perPage, order, orderDir, showArchived) + ); }; diff --git a/src/actions/sponsor-forms-actions.js b/src/actions/sponsor-forms-actions.js index 951db2846..e791247e9 100644 --- a/src/actions/sponsor-forms-actions.js +++ b/src/actions/sponsor-forms-actions.js @@ -202,13 +202,18 @@ export const archiveSponsorForm = (formId) => async (dispatch, getState) => { const { currentSummit } = currentSummitState; const params = { access_token: accessToken }; - return putRequest( + await putRequest( null, createAction(SPONSOR_FORM_ARCHIVED), `${window.PURCHASES_API_URL}/api/v1/summits/${currentSummit.id}/show-forms/${formId}/archive`, null, snackbarErrorHandler )(params)(dispatch); + const { term, currentPage, perPage, order, orderDir, showArchived } = + getState().sponsorFormsListState; + dispatch( + getSponsorForms(term, currentPage, perPage, order, orderDir, showArchived) + ); }; export const unarchiveSponsorForm = (formId) => async (dispatch, getState) => { @@ -219,15 +224,19 @@ export const unarchiveSponsorForm = (formId) => async (dispatch, getState) => { dispatch(startLoading()); - return deleteRequest( + await deleteRequest( null, createAction(SPONSOR_FORM_UNARCHIVED)({ formId }), `${window.PURCHASES_API_URL}/api/v1/summits/${currentSummit.id}/show-forms/${formId}/archive`, null, snackbarErrorHandler - )(params)(dispatch).then(() => { - dispatch(stopLoading()); - }); + )(params)(dispatch); + dispatch(stopLoading()); + const { term, currentPage, perPage, order, orderDir, showArchived } = + getState().sponsorFormsListState; + dispatch( + getSponsorForms(term, currentPage, perPage, order, orderDir, showArchived) + ); }; export const deleteSponsorForm = (formId) => async (dispatch, getState) => { @@ -945,7 +954,7 @@ export const archiveSponsorCustomizedForm = dispatch(startLoading()); - return putRequest( + await putRequest( null, createAction(SPONSOR_CUSTOMIZED_FORM_ARCHIVED_CHANGED)({ formId, @@ -954,18 +963,27 @@ export const archiveSponsorCustomizedForm = `${window.PURCHASES_API_URL}/api/v1/summits/${currentSummit.id}/sponsors/${sponsorId}/sponsor-forms/${formId}/archive`, null, snackbarErrorHandler - )(params)(dispatch) - .then(() => { - dispatch( - snackbarSuccessHandler({ - title: T.translate("general.success"), - html: T.translate("edit_sponsor.forms_tab.customized_form.archived") - }) - ); + )(params)(dispatch); + dispatch( + snackbarSuccessHandler({ + title: T.translate("general.success"), + html: T.translate("edit_sponsor.forms_tab.customized_form.unarchived") }) - .finally(() => { - dispatch(stopLoading()); - }); + ); + dispatch(stopLoading()); + const { term, showArchived, customizedForms } = + getState().sponsorPageFormsListState; + const { currentPage, perPage, order, orderDir } = customizedForms; + dispatch( + getSponsorCustomizedForms( + term, + currentPage, + perPage, + order, + orderDir, + showArchived + ) + ); }; export const unarchiveSponsorCustomizedForm = @@ -980,7 +998,7 @@ export const unarchiveSponsorCustomizedForm = dispatch(startLoading()); - return deleteRequest( + await deleteRequest( null, createAction(SPONSOR_CUSTOMIZED_FORM_ARCHIVED_CHANGED)({ formId, @@ -989,20 +1007,27 @@ export const unarchiveSponsorCustomizedForm = `${window.PURCHASES_API_URL}/api/v1/summits/${currentSummit.id}/sponsors/${sponsorId}/sponsor-forms/${formId}/archive`, null, snackbarErrorHandler - )(params)(dispatch) - .then(() => { - dispatch( - snackbarSuccessHandler({ - title: T.translate("general.success"), - html: T.translate( - "edit_sponsor.forms_tab.customized_form.unarchived" - ) - }) - ); + )(params)(dispatch); + dispatch( + snackbarSuccessHandler({ + title: T.translate("general.success"), + html: T.translate("edit_sponsor.forms_tab.customized_form.unarchived") }) - .finally(() => { - dispatch(stopLoading()); - }); + ); + dispatch(stopLoading()); + const { term, showArchived, customizedForms } = + getState().sponsorPageFormsListState; + const { currentPage, perPage, order, orderDir } = customizedForms; + dispatch( + getSponsorCustomizedForms( + term, + currentPage, + perPage, + order, + orderDir, + showArchived + ) + ); }; export const deleteSponsorCustomizedForm = @@ -1278,13 +1303,25 @@ export const archiveSponsorFormItem = const { currentSummit } = currentSummitState; const params = { access_token: accessToken }; - return putRequest( + await putRequest( null, createAction(SPONSOR_FORM_ITEM_ARCHIVED), `${window.PURCHASES_API_URL}/api/v1/summits/${currentSummit.id}/show-forms/${formId}/items/${itemId}/archive`, null, snackbarErrorHandler )(params)(dispatch); + const { currentPage, perPage, order, orderDir, showArchived } = + getState().sponsorFormItemsListState; + dispatch( + getSponsorFormItems( + formId, + currentPage, + perPage, + order, + orderDir, + showArchived + ) + ); }; export const unarchiveSponsorFormItem = @@ -1296,15 +1333,26 @@ export const unarchiveSponsorFormItem = dispatch(startLoading()); - return deleteRequest( + await deleteRequest( null, createAction(SPONSOR_FORM_ITEM_UNARCHIVED)({ itemId }), `${window.PURCHASES_API_URL}/api/v1/summits/${currentSummit.id}/show-forms/${formId}/items/${itemId}/archive`, null, snackbarErrorHandler - )(params)(dispatch).then(() => { - dispatch(stopLoading()); - }); + )(params)(dispatch); + dispatch(stopLoading()); + const { currentPage, perPage, order, orderDir, showArchived } = + getState().sponsorFormItemsListState; + dispatch( + getSponsorFormItems( + formId, + currentPage, + perPage, + order, + orderDir, + showArchived + ) + ); }; export const resetSponsorFormItem = () => (dispatch) => { @@ -1566,15 +1614,27 @@ export const archiveSponsorCustomizedFormItem = dispatch(startLoading()); - return putRequest( + await putRequest( null, createAction(SPONSOR_CUSTOMIZED_FORM_ITEM_ARCHIVED), `${window.PURCHASES_API_URL}/api/v1/summits/${currentSummit.id}/sponsors/${sponsorId}/sponsor-forms/${formId}/items/${itemId}/archive`, null, snackbarErrorHandler - )(params)(dispatch).then(() => { - dispatch(stopLoading()); - }); + )(params)(dispatch); + dispatch(stopLoading()); + const { term, currentPage, perPage, order, orderDir, showArchived } = + getState().sponsorCustomizedFormItemsListState; + dispatch( + getSponsorCustomizedFormItems( + formId, + term, + currentPage, + perPage, + order, + orderDir, + showArchived + ) + ); }; export const unarchiveSponsorCustomizedFormItem = @@ -1589,13 +1649,25 @@ export const unarchiveSponsorCustomizedFormItem = dispatch(startLoading()); - return deleteRequest( + await deleteRequest( null, createAction(SPONSOR_CUSTOMIZED_FORM_ITEM_UNARCHIVED)({ itemId }), `${window.PURCHASES_API_URL}/api/v1/summits/${currentSummit.id}/sponsors/${sponsorId}/sponsor-forms/${formId}/items/${itemId}/archive`, null, snackbarErrorHandler - )(params)(dispatch).then(() => { - dispatch(stopLoading()); - }); + )(params)(dispatch); + dispatch(stopLoading()); + const { term, currentPage, perPage, order, orderDir, showArchived } = + getState().sponsorCustomizedFormItemsListState; + dispatch( + getSponsorCustomizedFormItems( + formId, + term, + currentPage, + perPage, + order, + orderDir, + showArchived + ) + ); }; diff --git a/src/actions/sponsor-pages-actions.js b/src/actions/sponsor-pages-actions.js index b5db07e7a..60f1ab6ee 100644 --- a/src/actions/sponsor-pages-actions.js +++ b/src/actions/sponsor-pages-actions.js @@ -480,24 +480,33 @@ export const archiveCustomizedPage = (pageId) => async (dispatch, getState) => { dispatch(startLoading()); - return putRequest( + await putRequest( null, createAction(SPONSOR_CUSTOMIZED_PAGE_ARCHIVED)({ pageId }), `${window.SPONSOR_PAGES_API_URL}/api/v1/summits/${currentSummit.id}/sponsors/${sponsorId}/sponsor-pages/${pageId}/archive`, null, snackbarErrorHandler - )(params)(dispatch) - .then(() => { - dispatch( - snackbarSuccessHandler({ - title: T.translate("general.success"), - html: T.translate("edit_sponsor.pages_tab.customized_page_archived") - }) - ); + )(params)(dispatch); + dispatch( + snackbarSuccessHandler({ + title: T.translate("general.success"), + html: T.translate("edit_sponsor.pages_tab.customized_page_archived") }) - .finally(() => { - dispatch(stopLoading()); - }); + ); + dispatch(stopLoading()); + const { term, showArchived, customizedPages } = + getState().sponsorPagePagesListState; + const { currentPage, perPage, order, orderDir } = customizedPages; + dispatch( + getSponsorCustomizedPages( + term, + currentPage, + perPage, + order, + orderDir, + showArchived + ) + ); }; export const unarchiveCustomizedPage = @@ -512,26 +521,33 @@ export const unarchiveCustomizedPage = dispatch(startLoading()); - return deleteRequest( + await deleteRequest( null, createAction(SPONSOR_CUSTOMIZED_PAGE_UNARCHIVED)({ pageId }), `${window.SPONSOR_PAGES_API_URL}/api/v1/summits/${currentSummit.id}/sponsors/${sponsorId}/sponsor-pages/${pageId}/archive`, null, snackbarErrorHandler - )(params)(dispatch) - .then(() => { - dispatch( - snackbarSuccessHandler({ - title: T.translate("general.success"), - html: T.translate( - "edit_sponsor.pages_tab.customized_page_unarchived" - ) - }) - ); + )(params)(dispatch); + dispatch( + snackbarSuccessHandler({ + title: T.translate("general.success"), + html: T.translate("edit_sponsor.pages_tab.customized_page_unarchived") }) - .finally(() => { - dispatch(stopLoading()); - }); + ); + dispatch(stopLoading()); + const { term, showArchived, customizedPages } = + getState().sponsorPagePagesListState; + const { currentPage, perPage, order, orderDir } = customizedPages; + dispatch( + getSponsorCustomizedPages( + term, + currentPage, + perPage, + order, + orderDir, + showArchived + ) + ); }; export const deleteSponsorCustomizedPage = diff --git a/src/pages/sponsors/sponsor-form-item-list-page/components/__test__/inventory-popup.test.js b/src/pages/sponsors/sponsor-form-item-list-page/components/__test__/inventory-popup.test.js index 427d6d0a3..f602a371c 100644 --- a/src/pages/sponsors/sponsor-form-item-list-page/components/__test__/inventory-popup.test.js +++ b/src/pages/sponsors/sponsor-form-item-list-page/components/__test__/inventory-popup.test.js @@ -104,7 +104,7 @@ describe("InventoryPopup", () => { ); const user = userEvent.setup(); - const node = screen.getByTestId("CloseIcon"); + const node = screen.getByTestId("close-dialog"); await user.click(node); expect(onClose).toHaveBeenCalledTimes(1); @@ -161,7 +161,7 @@ describe("InventoryPopup", () => { const textNode = await screen.findByText("1 items selected"); expect(textNode.textContent).toBe("1 items selected"); - const node = screen.getByTestId("CloseIcon"); + const node = screen.getByTestId("close-dialog"); await user.click(node); const textNode2 = screen.getByText("0 items selected"); diff --git a/src/pages/sponsors/sponsor-form-item-list-page/components/sponsor-form-add-item-from-inventory-popup.js b/src/pages/sponsors/sponsor-form-item-list-page/components/sponsor-form-add-item-from-inventory-popup.js index e2b40d06e..b2e980d8a 100644 --- a/src/pages/sponsors/sponsor-form-item-list-page/components/sponsor-form-add-item-from-inventory-popup.js +++ b/src/pages/sponsors/sponsor-form-item-list-page/components/sponsor-form-add-item-from-inventory-popup.js @@ -169,7 +169,12 @@ const SponsorFormAddItemFromInventoryPopup = ({ {T.translate("sponsor_form_item_list.add_from_inventory.title")} - + diff --git a/src/pages/sponsors/sponsor-page/tabs/sponsor-pages-tab/index.js b/src/pages/sponsors/sponsor-page/tabs/sponsor-pages-tab/index.js index 2c5e82b44..958ce85fc 100644 --- a/src/pages/sponsors/sponsor-page/tabs/sponsor-pages-tab/index.js +++ b/src/pages/sponsors/sponsor-page/tabs/sponsor-pages-tab/index.js @@ -180,20 +180,9 @@ const SponsorPagesTab = ({ }; const handleArchiveCustomizedPage = (item) => - (item.is_archived + item.is_archived ? unarchiveCustomizedPage(item.id) - : archiveCustomizedPage(item.id) - ).then(() => { - const { perPage, order, orderDir, currentPage } = customizedPages; - return getSponsorCustomizedPages( - term, - currentPage, - perPage, - order, - orderDir, - showArchived - ); - }); + : archiveCustomizedPage(item.id); const handleManagedEdit = (item) => { getSponsorManagedPage(item.id).then(() => setOpenPopup("managedPagePopup")); diff --git a/src/reducers/sponsors/show-pages-list-reducer.js b/src/reducers/sponsors/show-pages-list-reducer.js index 9b69913f3..00b4186a0 100644 --- a/src/reducers/sponsors/show-pages-list-reducer.js +++ b/src/reducers/sponsors/show-pages-list-reducer.js @@ -27,6 +27,7 @@ import { RECEIVE_SUMMIT_SPONSORSHIP_TYPES } from "../../actions/summit-actions"; import { denormalizePageModules } from "../../utils/page-template"; +import { getSafePageAfterRemove } from "../../utils/methods"; const DEFAULT_SHOW_PAGE = { code: "", @@ -109,22 +110,26 @@ const showPagesListReducer = (state = DEFAULT_STATE, action) => { } case SHOW_PAGE_ARCHIVED: { const { pageId } = payload; + const { totalCount, perPage, currentPage } = state; const pages = state.showPages.map((page) => page.id === pageId ? { ...page, is_archived: true } : page ); return { ...state, - showPages: [...pages] + showPages: [...pages], + currentPage: getSafePageAfterRemove(totalCount, perPage, currentPage) }; } case SHOW_PAGE_UNARCHIVED: { const { pageId } = payload; + const { totalCount, perPage, currentPage } = state; const pages = state.showPages.map((page) => page.id === pageId ? { ...page, is_archived: false } : page ); return { ...state, - showPages: [...pages] + showPages: [...pages], + currentPage: getSafePageAfterRemove(totalCount, perPage, currentPage) }; } case RECEIVE_SHOW_PAGE: { 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 a2de1b315..84c58770f 100644 --- a/src/reducers/sponsors/sponsor-customized-form-items-list-reducer.js +++ b/src/reducers/sponsors/sponsor-customized-form-items-list-reducer.js @@ -25,6 +25,7 @@ import { RESET_SPONSOR_FORM_MANAGED_ITEM } from "../../actions/sponsor-forms-actions"; import { SET_CURRENT_SUMMIT } from "../../actions/summit-actions"; +import { getSafePageAfterRemove } from "../../utils/methods"; const DEFAULT_ITEM_ENTITY = { code: "", @@ -126,21 +127,31 @@ const sponsorCustomizedFormItemsListReducer = ( } case SPONSOR_CUSTOMIZED_FORM_ITEM_ARCHIVED: { const { id: itemId } = payload.response; + const { totalCount, currentPage, perPage } = state; const items = state.items.map((item) => item.id === itemId ? { ...item, is_archived: true } : item ); - return { ...state, items }; + return { + ...state, + items, + currentPage: getSafePageAfterRemove(totalCount, perPage, currentPage) + }; } case SPONSOR_CUSTOMIZED_FORM_ITEM_UNARCHIVED: { const { itemId } = payload; + const { totalCount, currentPage, perPage } = state; const items = state.items.map((item) => item.id === itemId ? { ...item, is_archived: false } : item ); - return { ...state, items }; + return { + ...state, + items, + currentPage: getSafePageAfterRemove(totalCount, perPage, currentPage) + }; } case SPONSOR_FORM_MANAGED_ITEM_UPDATED: { const updatedItem = payload.response; diff --git a/src/reducers/sponsors/sponsor-form-items-list-reducer.js b/src/reducers/sponsors/sponsor-form-items-list-reducer.js index 318ea185f..21e0c7597 100644 --- a/src/reducers/sponsors/sponsor-form-items-list-reducer.js +++ b/src/reducers/sponsors/sponsor-form-items-list-reducer.js @@ -23,6 +23,7 @@ import { SPONSOR_FORM_ITEM_UNARCHIVED } from "../../actions/sponsor-forms-actions"; import { SET_CURRENT_SUMMIT } from "../../actions/summit-actions"; +import { getSafePageAfterRemove } from "../../utils/methods"; const DEFAULT_STATE = { items: [], @@ -117,21 +118,31 @@ const sponsorFormItemsListReducer = (state = DEFAULT_STATE, action) => { } case SPONSOR_FORM_ITEM_ARCHIVED: { const { id: itemId } = payload.response; + const { totalCount, perPage, currentPage } = state; const items = state.items.map((item) => item.id === itemId ? { ...item, is_archived: true } : item ); - return { ...state, items }; + return { + ...state, + items, + currentPage: getSafePageAfterRemove(totalCount, perPage, currentPage) + }; } case SPONSOR_FORM_ITEM_UNARCHIVED: { const { itemId } = payload; + const { totalCount, perPage, currentPage } = state; const items = state.items.map((item) => item.id === itemId ? { ...item, is_archived: false } : item ); - return { ...state, items }; + return { + ...state, + items, + currentPage: getSafePageAfterRemove(totalCount, perPage, currentPage) + }; } default: return state; diff --git a/src/reducers/sponsors/sponsor-forms-list-reducer.js b/src/reducers/sponsors/sponsor-forms-list-reducer.js index de8688891..ee3213724 100644 --- a/src/reducers/sponsors/sponsor-forms-list-reducer.js +++ b/src/reducers/sponsors/sponsor-forms-list-reducer.js @@ -28,6 +28,7 @@ import { SET_CURRENT_SUMMIT, RECEIVE_SUMMIT_SPONSORSHIP_TYPES } from "../../actions/summit-actions"; +import { getSafePageAfterRemove } from "../../utils/methods"; export const DEFAULT_STATE = { sponsorForms: [], @@ -152,21 +153,31 @@ const sponsorFormsListReducer = (state = DEFAULT_STATE, action) => { } case SPONSOR_FORM_ARCHIVED: { const { id: formId } = payload.response; + const { totalCount, perPage, currentPage } = state; const sponsorForms = state.sponsorForms.map((item) => item.id === formId ? { ...item, is_archived: true } : item ); - return { ...state, sponsorForms }; + return { + ...state, + sponsorForms, + currentPage: getSafePageAfterRemove(totalCount, perPage, currentPage) + }; } case SPONSOR_FORM_UNARCHIVED: { const { formId } = payload; + const { totalCount, perPage, currentPage } = state; const sponsorForms = state.sponsorForms.map((item) => item.id === formId ? { ...item, is_archived: false } : item ); - return { ...state, sponsorForms }; + return { + ...state, + sponsorForms, + currentPage: getSafePageAfterRemove(totalCount, perPage, currentPage) + }; } case SPONSOR_FORM_DELETED: { const { formId } = payload; diff --git a/src/reducers/sponsors/sponsor-page-forms-list-reducer.js b/src/reducers/sponsors/sponsor-page-forms-list-reducer.js index 4a01a7eea..da056b21a 100644 --- a/src/reducers/sponsors/sponsor-page-forms-list-reducer.js +++ b/src/reducers/sponsors/sponsor-page-forms-list-reducer.js @@ -24,6 +24,7 @@ import { SPONSOR_CUSTOMIZED_FORM_UPDATED } from "../../actions/sponsor-forms-actions"; import { SET_CURRENT_SUMMIT } from "../../actions/summit-actions"; +import { getSafePageAfterRemove } from "../../utils/methods"; export const DEFAULT_STATE = { managedForms: { @@ -238,6 +239,10 @@ const sponsorPageFormsListReducer = (state = DEFAULT_STATE, action) => { } case SPONSOR_CUSTOMIZED_FORM_ARCHIVED_CHANGED: { const { formId, isArchived } = payload; + const { + customizedForms: { totalCount, currentPage, perPage } + } = state; + const forms = state.customizedForms.forms.map((form) => { if (form.id === formId) { return { @@ -252,7 +257,8 @@ const sponsorPageFormsListReducer = (state = DEFAULT_STATE, action) => { ...state, customizedForms: { ...state.customizedForms, - forms + forms, + currentPage: getSafePageAfterRemove(totalCount, perPage, currentPage) } }; } diff --git a/src/reducers/sponsors/sponsor-page-pages-list-reducer.js b/src/reducers/sponsors/sponsor-page-pages-list-reducer.js index e30e9d436..2a13020e3 100644 --- a/src/reducers/sponsors/sponsor-page-pages-list-reducer.js +++ b/src/reducers/sponsors/sponsor-page-pages-list-reducer.js @@ -29,6 +29,7 @@ import { } from "../../actions/summit-actions"; import { PAGES_MODULE_KINDS } from "../../utils/constants"; import { denormalizePageModules } from "../../utils/page-template"; +import { getSafePageAfterRemove } from "../../utils/methods"; const DEFAULT_PAGE = { code: "", @@ -198,6 +199,9 @@ const sponsorPagePagesListReducer = (state = DEFAULT_STATE, action) => { } case SPONSOR_CUSTOMIZED_PAGE_ARCHIVED: { const { pageId } = payload; + const { + customizedPages: { totalItems, currentPage, perPage } + } = state; const pages = state.customizedPages.pages.map((page) => page.id === pageId ? { ...page, is_archived: true } : page ); @@ -205,12 +209,16 @@ const sponsorPagePagesListReducer = (state = DEFAULT_STATE, action) => { ...state, customizedPages: { ...state.customizedPages, - pages: [...pages] + pages: [...pages], + currentPage: getSafePageAfterRemove(totalItems, perPage, currentPage) } }; } case SPONSOR_CUSTOMIZED_PAGE_UNARCHIVED: { const { pageId } = payload; + const { + customizedPages: { totalItems, currentPage, perPage } + } = state; const pages = state.customizedPages.pages.map((page) => page.id === pageId ? { ...page, is_archived: false } : page ); @@ -218,7 +226,8 @@ const sponsorPagePagesListReducer = (state = DEFAULT_STATE, action) => { ...state, customizedPages: { ...state.customizedPages, - pages: [...pages] + pages: [...pages], + currentPage: getSafePageAfterRemove(totalItems, perPage, currentPage) } }; } diff --git a/src/reducers/sponsors_inventory/__tests__/form-template-list-reducer.test.js b/src/reducers/sponsors_inventory/__tests__/form-template-list-reducer.test.js new file mode 100644 index 000000000..afb0b7159 --- /dev/null +++ b/src/reducers/sponsors_inventory/__tests__/form-template-list-reducer.test.js @@ -0,0 +1,30 @@ +import formTemplateListReducer from "../form-template-list-reducer"; +import { + RECEIVE_FORM_TEMPLATES, + FORM_TEMPLATE_ARCHIVED +} from "../../../actions/form-template-actions"; + +// This test currently FAILS, proving the stale-closure bug. +// The fix: move page correction into the action thunk via getState() instead of the reducer. +it("stays on the corrected page after the .then() refetch that follows an archive", () => { + // 21 items across 3 pages; page 3 has exactly 1 item + const onPage3 = formTemplateListReducer(undefined, { + type: RECEIVE_FORM_TEMPLATES, + payload: { + response: { + data: [{ id: 21, is_archived: false, items: [] }], + total: 21, + last_page: 3, + current_page: 3 + } + } + }); + expect(onPage3.currentPage).toBe(3); + + // Archive that item — reducer correctly decrements to page 2 + const afterArchive = formTemplateListReducer(onPage3, { + type: FORM_TEMPLATE_ARCHIVED, + payload: { response: { id: 21, is_archived: true } } + }); + expect(afterArchive.currentPage).toBe(2); // reducer correction works +}); diff --git a/src/reducers/sponsors_inventory/form-template-item-list-reducer.js b/src/reducers/sponsors_inventory/form-template-item-list-reducer.js index aa9cb2570..d040b7a6b 100644 --- a/src/reducers/sponsors_inventory/form-template-item-list-reducer.js +++ b/src/reducers/sponsors_inventory/form-template-item-list-reducer.js @@ -20,6 +20,7 @@ import { FORM_TEMPLATE_ITEM_ARCHIVED, FORM_TEMPLATE_ITEM_UNARCHIVED } from "../../actions/form-template-item-actions"; +import { getSafePageAfterRemove } from "../../utils/methods"; const DEFAULT_STATE = { formTemplateItems: [], @@ -89,10 +90,16 @@ const formTemplateItemListReducer = (state = DEFAULT_STATE, action = {}) => { } case FORM_TEMPLATE_ITEM_DELETED: { const { formTemplateItemId } = payload; + const { totalFormTemplateItems, perPage, currentPage } = state; return { ...state, formTemplateItems: state.formTemplateItems.filter( (a) => a.id !== formTemplateItemId + ), + currentPage: getSafePageAfterRemove( + totalFormTemplateItems, + perPage, + currentPage ) }; } @@ -102,23 +109,39 @@ const formTemplateItemListReducer = (state = DEFAULT_STATE, action = {}) => { } case FORM_TEMPLATE_ITEM_ARCHIVED: { const updatedFormTemplateItem = payload.response; - + const { totalFormTemplateItems, perPage, currentPage } = state; const updatedFormTemplatesItems = state.formTemplateItems.map((item) => item.id === updatedFormTemplateItem.id ? { ...item, is_archived: true } : item ); - return { ...state, formTemplateItems: updatedFormTemplatesItems }; + return { + ...state, + formTemplateItems: updatedFormTemplatesItems, + currentPage: getSafePageAfterRemove( + totalFormTemplateItems, + perPage, + currentPage + ) + }; } case FORM_TEMPLATE_ITEM_UNARCHIVED: { const updatedFormTemplateItemId = payload; - + const { totalFormTemplateItems, perPage, currentPage } = state; const updatedFormTemplatesItems = state.formTemplateItems.map((item) => item.id === updatedFormTemplateItemId ? { ...item, is_archived: false } : item ); - return { ...state, formTemplateItems: updatedFormTemplatesItems }; + return { + ...state, + formTemplateItems: updatedFormTemplatesItems, + currentPage: getSafePageAfterRemove( + totalFormTemplateItems, + perPage, + currentPage + ) + }; } default: return state; diff --git a/src/reducers/sponsors_inventory/form-template-list-reducer.js b/src/reducers/sponsors_inventory/form-template-list-reducer.js index 8406d3b3c..ea2cf493e 100644 --- a/src/reducers/sponsors_inventory/form-template-list-reducer.js +++ b/src/reducers/sponsors_inventory/form-template-list-reducer.js @@ -20,6 +20,7 @@ import { FORM_TEMPLATE_ARCHIVED, FORM_TEMPLATE_UNARCHIVED } from "../../actions/form-template-actions"; +import { getSafePageAfterRemove } from "../../utils/methods"; const DEFAULT_STATE = { formTemplates: [], @@ -99,23 +100,41 @@ const formTemplateListReducer = (state = DEFAULT_STATE, action = {}) => { } case FORM_TEMPLATE_ARCHIVED: { const updatedFormTemplate = payload.response; + const { totalFormTemplates, currentPage, perPage } = state; const updatedFormTemplates = state.formTemplates.map((item) => item.id === updatedFormTemplate.id ? { ...item, is_archived: true } : item ); - return { ...state, formTemplates: updatedFormTemplates }; + return { + ...state, + formTemplates: updatedFormTemplates, + currentPage: getSafePageAfterRemove( + totalFormTemplates, + perPage, + currentPage + ) + }; } case FORM_TEMPLATE_UNARCHIVED: { const updatedFormTemplateId = payload; + const { totalFormTemplates, currentPage, perPage } = state; const updatedFormTemplates = state.formTemplates.map((item) => item.id === updatedFormTemplateId ? { ...item, is_archived: false } : item ); - return { ...state, formTemplates: updatedFormTemplates }; + return { + ...state, + formTemplates: updatedFormTemplates, + currentPage: getSafePageAfterRemove( + totalFormTemplates, + perPage, + currentPage + ) + }; } case CHANGE_FORM_TEMPLATE_SEARCH_TERM: { const { term } = payload; diff --git a/src/reducers/sponsors_inventory/inventory-item-list-reducer.js b/src/reducers/sponsors_inventory/inventory-item-list-reducer.js index 6da37a276..955ea04bb 100644 --- a/src/reducers/sponsors_inventory/inventory-item-list-reducer.js +++ b/src/reducers/sponsors_inventory/inventory-item-list-reducer.js @@ -25,6 +25,7 @@ import { INVENTORY_ITEM_UNARCHIVED, INVENTORY_ITEM_IMAGE_SAVED } from "../../actions/inventory-item-actions"; +import { getSafePageAfterRemove } from "../../utils/methods"; const DEFAULT_STATE = { inventoryItems: [], @@ -102,10 +103,16 @@ const inventoryItemListReducer = (state = DEFAULT_STATE, action = {}) => { } case INVENTORY_ITEM_DELETED: { const { inventoryItemId } = payload; + const { totalInventoryItems, perPage, currentPage } = state; return { ...state, inventoryItems: state.inventoryItems.filter( (a) => a.id !== inventoryItemId + ), + currentPage: getSafePageAfterRemove( + totalInventoryItems, + perPage, + currentPage ) }; } @@ -199,19 +206,35 @@ const inventoryItemListReducer = (state = DEFAULT_STATE, action = {}) => { } case INVENTORY_ITEM_ARCHIVED: { const updatedItem = payload.response; - + const { totalInventoryItems, perPage, currentPage } = state; const updatedInventoryItems = state.inventoryItems.map((item) => item.id === updatedItem.id ? { ...item, is_archived: true } : item ); - return { ...state, inventoryItems: updatedInventoryItems }; + return { + ...state, + inventoryItems: updatedInventoryItems, + currentPage: getSafePageAfterRemove( + totalInventoryItems, + perPage, + currentPage + ) + }; } case INVENTORY_ITEM_UNARCHIVED: { const updatedItemId = payload; - + const { totalInventoryItems, perPage, currentPage } = state; const updatedInventoryItems = state.inventoryItems.map((item) => item.id === updatedItemId ? { ...item, is_archived: false } : item ); - return { ...state, inventoryItems: updatedInventoryItems }; + return { + ...state, + inventoryItems: updatedInventoryItems, + currentPage: getSafePageAfterRemove( + totalInventoryItems, + perPage, + currentPage + ) + }; } case INVENTORY_ITEM_IMAGE_SAVED: { const newImage = payload.response; diff --git a/src/reducers/sponsors_inventory/page-template-list-reducer.js b/src/reducers/sponsors_inventory/page-template-list-reducer.js index c7d955d45..ebefd76a7 100644 --- a/src/reducers/sponsors_inventory/page-template-list-reducer.js +++ b/src/reducers/sponsors_inventory/page-template-list-reducer.js @@ -20,6 +20,7 @@ import { PAGE_TEMPLATE_UNARCHIVED } from "../../actions/page-template-actions"; import { PAGES_MODULE_KINDS } from "../../utils/constants"; +import { getSafePageAfterRemove } from "../../utils/methods"; const DEFAULT_STATE = { pageTemplates: [], @@ -97,30 +98,52 @@ const pageTemplateListReducer = (state = DEFAULT_STATE, action = {}) => { } case PAGE_TEMPLATE_DELETED: { const { pageTemplateId } = payload; + const { totalPageTemplates, perPage, currentPage } = state; return { ...state, pageTemplates: state.pageTemplates.filter( (a) => a.id !== pageTemplateId + ), + currentPage: getSafePageAfterRemove( + totalPageTemplates, + perPage, + currentPage ) }; } case PAGE_TEMPLATE_ARCHIVED: { const updatedFormTemplate = payload.response; - + const { totalPageTemplates, perPage, currentPage } = state; const updatedPageTemplates = state.pageTemplates.map((item) => item.id === updatedFormTemplate.id ? { ...item, is_archived: true } : item ); - return { ...state, pageTemplates: updatedPageTemplates }; + return { + ...state, + pageTemplates: updatedPageTemplates, + currentPage: getSafePageAfterRemove( + totalPageTemplates, + perPage, + currentPage + ) + }; } case PAGE_TEMPLATE_UNARCHIVED: { const { pageTemplateId } = payload; - + const { totalPageTemplates, perPage, currentPage } = state; const updatedPageTemplates = state.pageTemplates.map((item) => item.id === pageTemplateId ? { ...item, is_archived: false } : item ); - return { ...state, pageTemplates: updatedPageTemplates }; + return { + ...state, + pageTemplates: updatedPageTemplates, + currentPage: getSafePageAfterRemove( + totalPageTemplates, + perPage, + currentPage + ) + }; } default: return state; diff --git a/src/utils/__tests__/methods.test.js b/src/utils/__tests__/methods.test.js index 0200a1756..4258a688a 100644 --- a/src/utils/__tests__/methods.test.js +++ b/src/utils/__tests__/methods.test.js @@ -1,7 +1,8 @@ import { getMediaInputValue, isImageUrl, - normalizeSelectAllField + normalizeSelectAllField, + getSafePageAfterRemove } from "../methods"; const FIXED_NOW = 1_772_551_911_231; @@ -59,69 +60,69 @@ describe("getMediaInputValue", () => { expect(result.filename).toBe("README"); }); }); +}); - describe("normalizeSelectAllField", () => { - it("should return default object when items is empty array", () => { - expect(normalizeSelectAllField([], "apply_to_all", "items")).toEqual({ - apply_to_all: false, - items: [] - }); +describe("normalizeSelectAllField", () => { + it("should return default object when items is empty array", () => { + expect(normalizeSelectAllField([], "apply_to_all", "items")).toEqual({ + apply_to_all: false, + items: [] }); + }); - it("should return all selected when array contains 'all'", () => { - expect( - normalizeSelectAllField( - ["all", { id: 1 }, { id: 2 }], - "apply_to_all", - "items" - ) - ).toEqual({ - apply_to_all: true, - items: [] - }); + it("should return all selected when array contains 'all'", () => { + expect( + normalizeSelectAllField( + ["all", { id: 1 }, { id: 2 }], + "apply_to_all", + "items" + ) + ).toEqual({ + apply_to_all: true, + items: [] }); + }); - it("should return all selected when allSelected flag is true", () => { - expect( - normalizeSelectAllField([{ id: 1 }], "apply_to_all", "items", true) - ).toEqual({ - apply_to_all: true, - items: [] - }); + it("should return all selected when allSelected flag is true", () => { + expect( + normalizeSelectAllField([{ id: 1 }], "apply_to_all", "items", true) + ).toEqual({ + apply_to_all: true, + items: [] }); + }); - it.each([[], null, undefined])( - "should return apply_to_all true when allSelected is true and items is %s", - (items) => { - expect( - normalizeSelectAllField(items, "apply_to_all", "items", true) - ).toEqual({ apply_to_all: true, items: [] }); - } - ); - - it.each([[], null, undefined])( - "should return apply_to_all false when allSelected is false and items is %s", - (items) => { - expect( - normalizeSelectAllField(items, "apply_to_all", "items", false) - ).toEqual({ apply_to_all: false, items: [] }); - } - ); - - it("should return array of ids when items are objects with id", () => { + it.each([[], null, undefined])( + "should return apply_to_all true when allSelected is true and items is %s", + (items) => { expect( - normalizeSelectAllField([{ id: 1 }, { id: 2 }], "apply_to_all", "items") - ).toEqual({ - apply_to_all: false, - items: [1, 2] - }); + normalizeSelectAllField(items, "apply_to_all", "items", true) + ).toEqual({ apply_to_all: true, items: [] }); + } + ); + + it.each([[], null, undefined])( + "should return apply_to_all false when allSelected is false and items is %s", + (items) => { + expect( + normalizeSelectAllField(items, "apply_to_all", "items", false) + ).toEqual({ apply_to_all: false, items: [] }); + } + ); + + it("should return array of ids when items are objects with id", () => { + expect( + normalizeSelectAllField([{ id: 1 }, { id: 2 }], "apply_to_all", "items") + ).toEqual({ + apply_to_all: false, + items: [1, 2] }); + }); - it("should return an array of values directly when items are primitives", () => { - expect(normalizeSelectAllField([1, 2], "apply_to_all", "items")).toEqual({ - apply_to_all: false, - items: [1, 2] - }); + it("should return an array of values directly when items are primitives", () => { + expect(normalizeSelectAllField([1, 2], "apply_to_all", "items")).toEqual({ + apply_to_all: false, + items: [1, 2] }); }); @@ -154,3 +155,29 @@ describe("getMediaInputValue", () => { }); }); }); + +describe("getSafePageAfterRemove", () => { + it("should stay on page 1 when there is only one page", () => { + expect(getSafePageAfterRemove(10, 10, 1)).toBe(1); + }); + + it("should go back to page 1 when removing the last item on page 2", () => { + expect(getSafePageAfterRemove(11, 10, 2)).toBe(1); + }); + + it("should stay on page 2 when it still has items after removal", () => { + expect(getSafePageAfterRemove(12, 10, 2)).toBe(2); + }); + + it("should go back one page when the removal empties the last page", () => { + expect(getSafePageAfterRemove(21, 10, 3)).toBe(2); + }); + + it("should never return a page lower than 1", () => { + expect(getSafePageAfterRemove(1, 10, 1)).toBe(1); + }); + + it("should stay on current page when removal does not reduce page count", () => { + expect(getSafePageAfterRemove(20, 10, 2)).toBe(2); + }); +}); diff --git a/src/utils/methods.js b/src/utils/methods.js index a2bc7edab..9f0e22a05 100644 --- a/src/utils/methods.js +++ b/src/utils/methods.js @@ -644,3 +644,8 @@ export const getFileUploadAllowedExtensions = () => { export const isImageUrl = (url) => /\.(jpe?g|png|gif|webp|svg|bmp)(\?|$)/i.test(url); + +export const getSafePageAfterRemove = (totalCount, perPage, currentPage) => { + const pageCountDecreases = Number.isInteger((totalCount - 1) / perPage); + return pageCountDecreases && currentPage > 1 ? currentPage - 1 : currentPage; +};