Skip to content

Commit 0ffda96

Browse files
fix: feedback from PR
Signed-off-by: Priscila Moneo <priscila_moneo@hotmail.com.ar>
1 parent ffb91e9 commit 0ffda96

8 files changed

Lines changed: 420 additions & 14 deletions

File tree

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
/**
2+
* @jest-environment jsdom
3+
*/
4+
import configureStore from "redux-mock-store";
5+
import thunk from "redux-thunk";
6+
import flushPromises from "flush-promises";
7+
import {
8+
postRequest,
9+
putRequest
10+
} from "openstack-uicore-foundation/lib/utils/actions";
11+
import { saveTaxType } from "../tax-actions";
12+
import * as methods from "../../utils/methods";
13+
14+
jest.mock("openstack-uicore-foundation/lib/utils/actions", () => ({
15+
__esModule: true,
16+
...jest.requireActual("openstack-uicore-foundation/lib/utils/actions"),
17+
postRequest: jest.fn(),
18+
putRequest: jest.fn()
19+
}));
20+
21+
const requestMock =
22+
(requestActionCreator, receiveActionCreator) => () => (dispatch) => {
23+
if (requestActionCreator && typeof requestActionCreator === "function") {
24+
dispatch(requestActionCreator({}));
25+
}
26+
return new Promise((resolve) => {
27+
if (typeof receiveActionCreator === "function") {
28+
dispatch(receiveActionCreator({ response: {} }));
29+
} else {
30+
dispatch(receiveActionCreator);
31+
}
32+
resolve({ response: {} });
33+
});
34+
};
35+
36+
const storeState = {
37+
currentSummitState: { currentSummit: { id: 1 } }
38+
};
39+
40+
describe("saveTaxType", () => {
41+
const middlewares = [thunk];
42+
const mockStore = configureStore(middlewares);
43+
44+
beforeEach(() => {
45+
jest.spyOn(methods, "getAccessTokenSafely").mockResolvedValue("TOKEN");
46+
postRequest.mockImplementation(requestMock);
47+
putRequest.mockImplementation(requestMock);
48+
});
49+
50+
afterEach(() => {
51+
jest.restoreAllMocks();
52+
});
53+
54+
describe("create path (entity has no id)", () => {
55+
it("returns a Promise that resolves with the response payload", async () => {
56+
const store = mockStore(storeState);
57+
const result = store.dispatch(
58+
saveTaxType({ name: "VAT", rate: 20, tax_id: "V1" })
59+
);
60+
expect(result).toBeInstanceOf(Promise);
61+
await expect(result).resolves.toEqual({ response: {} });
62+
});
63+
64+
it("dispatches TAX_TYPE_ADDED then STOP_LOADING on success", async () => {
65+
const store = mockStore(storeState);
66+
store.dispatch(saveTaxType({ name: "VAT", rate: 20, tax_id: "V1" }));
67+
await flushPromises();
68+
69+
const actionTypes = store.getActions().map((a) => a.type);
70+
expect(actionTypes).toContain("TAX_TYPE_ADDED");
71+
expect(actionTypes).toContain("STOP_LOADING");
72+
expect(actionTypes.indexOf("STOP_LOADING")).toBeGreaterThan(
73+
actionTypes.indexOf("TAX_TYPE_ADDED")
74+
);
75+
});
76+
});
77+
78+
describe("update path (entity has id)", () => {
79+
it("returns a Promise that resolves with the response payload", async () => {
80+
const store = mockStore(storeState);
81+
const result = store.dispatch(
82+
saveTaxType({ id: 1, name: "VAT", rate: 20, tax_id: "V1" })
83+
);
84+
expect(result).toBeInstanceOf(Promise);
85+
await expect(result).resolves.toEqual({ response: {} });
86+
});
87+
88+
it("dispatches TAX_TYPE_UPDATED then STOP_LOADING on success", async () => {
89+
const store = mockStore(storeState);
90+
store.dispatch(
91+
saveTaxType({ id: 1, name: "VAT", rate: 20, tax_id: "V1" })
92+
);
93+
await flushPromises();
94+
95+
const actionTypes = store.getActions().map((a) => a.type);
96+
expect(actionTypes).toContain("TAX_TYPE_UPDATED");
97+
expect(actionTypes).toContain("STOP_LOADING");
98+
expect(actionTypes.indexOf("STOP_LOADING")).toBeGreaterThan(
99+
actionTypes.indexOf("TAX_TYPE_UPDATED")
100+
);
101+
});
102+
});
103+
});

src/actions/tax-actions.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,7 @@ export const saveTaxType = (entity) => async (dispatch, getState) => {
143143
authErrorHandler,
144144
entity
145145
)(params)(dispatch).then((payload) => {
146+
dispatch(stopLoading());
146147
dispatch(showSuccessMessage(T.translate("edit_tax_type.tax_type_saved")));
147148
return payload;
148149
});
@@ -155,6 +156,7 @@ export const saveTaxType = (entity) => async (dispatch, getState) => {
155156
authErrorHandler,
156157
entity
157158
)(params)(dispatch).then((payload) => {
159+
dispatch(stopLoading());
158160
dispatch(showSuccessMessage(T.translate("edit_tax_type.tax_type_created")));
159161
return payload;
160162
});

src/components/forms/tax-type-form.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,8 @@ const TaxTypeForm = ({
3939
currentSummit,
4040
onTicketLink,
4141
onTicketUnLink,
42-
onSubmit
42+
onSubmit,
43+
isSaving = false
4344
}) => {
4445
const initialValues = {
4546
id: entityProp.id,
@@ -134,7 +135,7 @@ const TaxTypeForm = ({
134135
</Box>
135136
)}
136137
<Stack direction="row" justifyContent="flex-end">
137-
<Button variant="contained" type="submit">
138+
<Button variant="contained" type="submit" disabled={isSaving}>
138139
{T.translate("general.save")}
139140
</Button>
140141
</Stack>

src/i18n/en.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1926,7 +1926,7 @@
19261926
"name": "Name",
19271927
"tax_id": "Tax Id",
19281928
"rate": "Rate",
1929-
"remove_warning": "Are you sure you want to delete tax type "
1929+
"remove_warning": "Please verify you want to delete tax type {taxType}"
19301930
},
19311931
"edit_tax_type": {
19321932
"tax_type": "Tax Type",
Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
import React from "react";
2+
import { act, screen } from "@testing-library/react";
3+
import userEvent from "@testing-library/user-event";
4+
import "@testing-library/jest-dom";
5+
import flushPromises from "flush-promises";
6+
import { renderWithRedux } from "../../../utils/test-utils";
7+
import TaxTypeListPage from "../tax-type-list-page";
8+
import {
9+
getTaxTypes,
10+
deleteTaxType,
11+
getTaxType,
12+
saveTaxType,
13+
resetTaxTypeForm,
14+
addTicketToTaxType,
15+
removeTicketFromTaxType
16+
} from "../../../actions/tax-actions";
17+
18+
jest.mock("../../../actions/tax-actions", () => ({
19+
getTaxTypes: jest.fn(),
20+
deleteTaxType: jest.fn(),
21+
getTaxType: jest.fn(),
22+
saveTaxType: jest.fn(),
23+
resetTaxTypeForm: jest.fn(),
24+
addTicketToTaxType: jest.fn(),
25+
removeTicketFromTaxType: jest.fn()
26+
}));
27+
28+
jest.mock("../../../routes/restrict", () => ({
29+
__esModule: true,
30+
default: (WrappedComponent) => WrappedComponent
31+
}));
32+
33+
jest.mock("react-breadcrumbs", () => ({
34+
Breadcrumb: () => null
35+
}));
36+
37+
jest.mock("openstack-uicore-foundation/lib/components/mui/table", () => ({
38+
__esModule: true,
39+
default: ({ onEdit, onDelete }) => (
40+
<div>
41+
<button
42+
type="button"
43+
onClick={() => onEdit({ id: 1, name: "VAT", rate: 20, tax_id: "V1" })}
44+
>
45+
edit-row
46+
</button>
47+
<button type="button" onClick={() => onDelete(1)}>
48+
delete-row
49+
</button>
50+
</div>
51+
)
52+
}));
53+
54+
jest.mock(
55+
"openstack-uicore-foundation/lib/components/mui/search-input",
56+
() => ({
57+
__esModule: true,
58+
default: () => <input placeholder="search-tax-types" />
59+
})
60+
);
61+
62+
jest.mock("../popup/tax-type-popup", () => ({
63+
__esModule: true,
64+
default: ({ onSubmit }) => (
65+
<div data-testid="tax-type-popup">
66+
<button
67+
type="button"
68+
onClick={() => onSubmit({ name: "New Tax", rate: 10, tax_id: "NT1" })}
69+
>
70+
popup-save
71+
</button>
72+
</div>
73+
)
74+
}));
75+
76+
jest.mock("i18n-react/dist/i18n-react", () => ({
77+
__esModule: true,
78+
default: { translate: (key) => key }
79+
}));
80+
81+
const initialState = {
82+
currentSummitState: {
83+
currentSummit: { id: 1 }
84+
},
85+
currentTaxTypeListState: {
86+
taxTypes: [{ id: 1, name: "VAT", rate: 20, tax_id: "V1" }],
87+
totalTaxTypes: 1,
88+
perPage: 10,
89+
currentPage: 1,
90+
term: "",
91+
order: "name",
92+
orderDir: 1
93+
},
94+
currentTaxTypeState: {
95+
entity: { id: 0, name: "", rate: "", tax_id: "", ticket_types: [] },
96+
errors: {}
97+
}
98+
};
99+
100+
describe("TaxTypeListPage", () => {
101+
beforeEach(() => {
102+
jest.clearAllMocks();
103+
getTaxTypes.mockReturnValue(() => Promise.resolve());
104+
deleteTaxType.mockReturnValue(() => Promise.resolve());
105+
saveTaxType.mockReturnValue(() => Promise.resolve());
106+
getTaxType.mockReturnValue(() => Promise.resolve());
107+
resetTaxTypeForm.mockReturnValue({ type: "RESET_TAX_TYPE_FORM" });
108+
addTicketToTaxType.mockReturnValue(() => Promise.resolve());
109+
removeTicketFromTaxType.mockReturnValue(() => Promise.resolve());
110+
});
111+
112+
it("reloads the list after a successful save", async () => {
113+
renderWithRedux(<TaxTypeListPage match={{ url: "/taxes" }} />, {
114+
initialState
115+
});
116+
117+
await userEvent.click(
118+
screen.getByRole("button", { name: "tax_type_list.add_tax_type" })
119+
);
120+
expect(screen.getByTestId("tax-type-popup")).toBeInTheDocument();
121+
122+
await act(async () => {
123+
await userEvent.click(screen.getByRole("button", { name: "popup-save" }));
124+
await flushPromises();
125+
});
126+
127+
// Call 1: useEffect on mount; call 2: handleSave .then()
128+
expect(getTaxTypes).toHaveBeenCalledTimes(2);
129+
});
130+
131+
it("reloads the list after a successful delete", async () => {
132+
renderWithRedux(<TaxTypeListPage match={{ url: "/taxes" }} />, {
133+
initialState
134+
});
135+
136+
await act(async () => {
137+
await userEvent.click(screen.getByRole("button", { name: "delete-row" }));
138+
await flushPromises();
139+
});
140+
141+
// Call 1: useEffect on mount; call 2: handleDelete .finally()
142+
expect(getTaxTypes).toHaveBeenCalledTimes(2);
143+
});
144+
145+
it("re-syncs the list after a failed delete", async () => {
146+
deleteTaxType.mockReturnValue(() =>
147+
Promise.reject(new Error("delete failed"))
148+
);
149+
150+
renderWithRedux(<TaxTypeListPage match={{ url: "/taxes" }} />, {
151+
initialState
152+
});
153+
154+
await act(async () => {
155+
await userEvent.click(screen.getByRole("button", { name: "delete-row" }));
156+
await flushPromises();
157+
});
158+
159+
// Call 1: useEffect on mount; call 2: handleDelete .finally() (fires even on rejection)
160+
expect(getTaxTypes).toHaveBeenCalledTimes(2);
161+
});
162+
});

0 commit comments

Comments
 (0)