I use a Jest test to make sure that a certain behavior always works.
- I wait for my app to load
- Make a mock RTK query request (with msw)
- Wait for requested data to get into the table in
contentTableBody
- Click a table row in
contentTableBody
to make aItemDetails
appear which shows some data passed with redux
Problem is that the test periodically fails due to not finding all of the table rows which leads me to believe there is some issue with asynchronisty of the code. How can I make sure that mock request always delivers all the data and the table is populated with it?
beforeEach(() => {
localStorage.setItem("userId", "991d3a3a-6e71-4869-9861-2b897d7a9deb");
localStorage.setItem(
"default-customer-991d3a3a-6e71-4869-9861-2b897d7a9deb",
JSON.stringify({
customerId: "eu.33008274",
customerNumber: "33008274",
customerName: 'ООО "АДАМАНТ СПб"',
street: "Глухоозерское ш.,д.12,лит.А,пом.8-Н,оф.2",
postalCode: "Глухоозерское ш.,д.1",
city: "г.Санкт-Петербург",
state: "78",
countryId: "RU",
})
);
});
jest.mock("react-i18next", () => ({
useTranslation: () => {
return {
t: (str) => str,
i18n: {
changeLanguage: () => new Promise(() => {}),
},
};
},
}));
jest.mock("hooks/useIsComponentEnabled", () => ({
useIsComponentEnabled: () => ({
getIsComponentEnabled: () => jest.fn(true),
isSuccess: true,
}),
}));
it("renders table correctly", async () => {
render(
<Provider store={store}>
<ThemeProvider theme={theme}>
<BrowserRouter>
<OrderList />
</BrowserRouter>
</ThemeProvider>
</Provider>
);
await waitFor(async () => {
const rows = await screen.findAllByRole("row");
expect(rows.length).toBeGreaterThan(1);
});
await waitFor(() => {
expect(
screen.getByText("67f9fdc8-ec0e-439a-9c4f-9a7307f4cd6e")
).toBeInTheDocument();
});
await userEvent.click(screen.getAllByRole("row").at(-1));
await waitFor(async () => {
await expect(
screen.getByTestId("itemDetailsContainer")
).toBeInTheDocument();
});
});
msw handler.js
export const handlers = [
rest.get("http://localhost/api/v1/order-items/", (req, res, ctx) => {
// successful response
return res(ctx.status(200), ctx.json(testOrderData), ctx.delay());
}),
rest.get("http://localhost/api/v1/customers/", (req, res, ctx) => {
// successful response
return res(ctx.status(200), ctx.json(testCustomerData), ctx.delay(30));
}),
rest.get("http://localhost/api/v1/sites/", (req, res, ctx) => {
// successful response
return res(ctx.status(200), ctx.json(testSitesData), ctx.delay(30));
}),
rest.get("http://localhost/api/v1/users/", (req, res, ctx) => {
// successful response
return res(ctx.status(200), ctx.json(testUserData), ctx.delay(30));
}),
rest.get(
"http://localhost/api/v1/users/991d3a3a-6e71-4869-9861-2b897d7a9deb/",
(req, res, ctx) => {
// successful response
return res(ctx.status(200), ctx.json(testUserData), ctx.delay(30));
}
),
];
OrderList.jsx
const OrderList = () => {
const isDesktop = useIsDesktop();
const { getIsComponentEnabled } = useIsComponentEnabled();
const isComponentEnabled = getIsComponentEnabled("OrderList");
const { t } = useTranslation();
if (!isComponentEnabled) return <Navigate to="/overview" replace={true} />;
return (
<PageWrapper>
<Box display="flex" justifyContent="space-between" alignItems="center">
<PageHeading>{t("order.title")}</PageHeading>
<OrderListOIButtons />
</Box>
<PageTypeProvider PageType="OrderList">
<MainContentGridContainer>
<LeftGrid>
{isDesktop ? (
<>
<PageFilters />
<ContentTable />
</>
) : (
<PageCard />
)}
</LeftGrid>
{isDesktop && (
<RightGrid>
<ItemDetails />
</RightGrid>
)}
</MainContentGridContainer>
</PageTypeProvider>
</PageWrapper>
);
};
contentTableBody.jsx
const ContentTableBody = ({ paginationModel, handlePagination }) => {
const { i18n } = useTranslation();
const apiRef = useGridApiRef();
const { isFetching, data, dataItem, columns, isInvoices } = useGetTableData();
const { openDetailsCard, closeDetailsCard, setOrderId } = useDetailsCard();
const {
openSelectedCard,
setDeliveriesToExport,
closeSelectedCard,
rowSelectionModel,
setRowSelectionModel,
} = useSelectedItemsCard(apiRef);
const openOrderCard = ({ id }) => {
setOrderId(id - 1);
openDetailsCard();
};
const handlePageSize = ({ pageSize, page }) => {
handlePagination({
page: paginationModel.pageSize !== pageSize ? 0 : page,
pageSize: pageSize,
});
};
const handleRowSelection = (rowIds) => {
setRowSelectionModel(rowIds);
if (isInvoices) return;
const deliveryIds = rowIds
.filter((rowId) => data.results[rowId - 1].deliveries.length)
.map((rowId) => data.results[rowId - 1].deliveries[0].deliveryId);
setDeliveriesToExport(deliveryIds);
if (rowIds.length) {
openDetailsCard();
openSelectedCard();
}
if (!rowIds.length) closeSelectedCard();
};
useEffect(() => {
if (isFetching) setRowSelectionModel([]);
}, [isFetching, setRowSelectionModel]);
useEffect(() => {
closeDetailsCard();
}, [closeDetailsCard]);
useEffect(() => {
if (!isFetching) {
apiRef.current.autosizeColumns({
includeHeaders: true,
includeOutliers: true,
});
}
}, [isFetching, apiRef]);
const rows = data?.results.map((item, index) => dataItem(item, index)) || [];
return (
<DataGrid
autosizeOptions={{
includeOutliers: true,
includeHeaders: true,
}}
autosizeOnMount
autoHeight
checkboxSelection
disableRowSelectionOnClick
disableColumnMenu
disableColumnResize
rowCount={data?.count || 0}
paginationMode="server"
columnHeaderHeight={40}
rows={rows}
loading={isFetching}
apiRef={apiRef}
columns={columns}
getRowHeight={() => "auto"}
pageSizeOptions={rowsPerPage}
onRowClick={openOrderCard}
sortingOrder={["desc", "asc", null]}
initialState={{
pagination: {
paginationModel: { page: 0, pageSize: 10 },
},
}}
paginationModel={paginationModel}
onPaginationModelChange={handlePageSize}
onRowSelectionModelChange={handleRowSelection}
rowSelectionModel={rowSelectionModel}
sx={OrderTableStyles}
localeText={
i18n.language === "ru"
? ruRU.components.MuiDataGrid.defaultProps.localeText
: null
}
/>
);
};
ItemDetails.jsx
const ItemDetails = () => {
const { isDetailsCardOpen } = useDetailsCard();
const { isSelectedOpen } = useSelectedItemsCard();
const { data, isInvoices } = useGetTableData();
const currentOrderId = useSelector((state) => state.contentTable.orderId);
const currentItemData = data?.results.length && data.results[currentOrderId];
const initialView = isInvoices ? <PaymentSummary /> : <NoDataOverlay />;
const content = isInvoices ? (
<InvoiceDetails currentInvoiceData={currentItemData} />
) : (
<OrderDetails currentOrderData={currentItemData} />
);
const renderDetails = () => {
if (isSelectedOpen) return <PageCardOrderExport />;
if (!currentItemData || !isDetailsCardOpen) return initialView;
return content;
};
return (
<Paper
data-testid="itemDetailsContainer"
elevation={4}
sx={(theme) => ({
padding: "16px",
display: "flex",
flexDirection: "column",
position: "sticky",
top: "10px",
overflowY: "auto",
height: "600px",
[theme.breakpoints.down("lgr")]: {
display: !isDetailsCardOpen && "none",
position: "fixed",
top: "200px",
right: "10px",
minWidth: "300px",
width: "40%",
animation: `${slideCard} .2s`,
zIndex: "6",
},
})}
>
{renderDetails()}
</Paper>
);
};
4