Hello dear stackoverflow users. I am developing an application with react and typescript. In this application, I write my tests with mocha sinon chai. After my test runs, I see these lines in uncovered lines. When I run it with debug, I see that my dList is an empty array, and since the test cannot find the dList, it cannot enter these lines in the test. Even though I have been researching for a long time, I have not found a solution yet. Can you help me on what I'm doing wrong?
my component code :
import { PageContainer } from '../../components/common/page-container/PageContainer';
import Filter from '../../components/common/filter/Filter';
import { FunctionComponent, useState, useEffect, useRef } from 'react';
import { CfaDatatable } from '@cfa-web-components/cfa-datatable/cfa-datatable-react';
import EditModal from '../../components/common/edit-modal/EditModal';
import { DefinitionBody } from '../../store/models/DefinitionModel';
import { useLazyGetDefinitionListQuery } from '../../store/apis/definitionApi';
import { TemplateResult, html } from 'lit';
import { BodyColumnType } from '@cfa-web-components/cfa-datatable/dist';
import { updateLoading } from '../../store/slices/loadingSlice';
import { useAppDispatch } from '../../store';
import styles from './PrimTab.module.scss';
interface EditDataTypes {
categoryName: string;
companyId: number;
explanationText: string;
isActive: string;
keyName: string;
valueName: string;
_index: number;
}
const PrimTab: FunctionComponent = () => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const dtref = useRef<any>(null);
const [editData, setEditData] = useState({});
const [isOpenDetailModal, setIsOpenDetailModal] = useState(false);
const [currentPageData, setCurrentPageData] = useState(1);
const [pageSize] = useState(20);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const [dList, setDlist] = useState<any>([]);
const [lastPage, setLastPage] = useState(1);
const [definitionBody, setDefinitionBody] = useState<DefinitionBody>({
pageInfo: {
currentPage: currentPageData < lastPage ? currentPageData : lastPage,
pageSize: pageSize,
sortByField: {
field: 'keyName',
direction: 'ASC'
}
},
categoryName: 'BOUNTY'
});
const [getDefinitions, { isLoading: loading, isFetching: definitionFetching }] = useLazyGetDefinitionListQuery();
const dispatch = useAppDispatch();
const cleanDt = (): void => {
if (dtref.current) {
const emAr = [];
setDlist(emAr);
}
};
const getDefinitionList = (): void => {
getDefinitions(definitionBody).then((response) => {
const payload = response.data;
setLastPage(Math.ceil((payload?.totalCount ?? 0) / pageSize));
cleanDt();
setDlist([...dList, ...payload?.resultList ?? []]);
});
};
useEffect(() => {
getDefinitionList();
}, [definitionBody]);
useEffect(() => {
dispatch(updateLoading(loading && definitionFetching));
}, [definitionFetching, loading]);
const editBtn = (value: EditDataTypes): TemplateResult => html`
<button
data-testid="edit-btn"
@click="${(): void => {
setIsOpenDetailModal(true);
setEditData(value);
}}"
style="
margin:0 20px;
color: rgb(25, 115, 184) !important;
font-size: 14px!important;
font-weight: 500!important;
cursor:pointer!important;
text-align:center!important;
background:transparent;
outline:none;
border:none;"
>
Düzenle
</button>
`;
const header = [{ label: 'Tanımlama Adı' }, { label: 'Açıklama' }, { label: 'Değer' }, { label: '' }];
const body: BodyColumnType[] = [
{ property: 'keyName' },
{ property: 'explanationText' },
{ property: 'valueName' },
{ property: 'aksiyon', temp: editBtn, align: 'center' }
];
const getNextData = (): void => {
if (currentPageData < lastPage && !loading) {
dispatch(updateLoading(true));
setTimeout(() => {
setCurrentPageData(currentPageData + 1);
setDefinitionBody({
pageInfo: {
currentPage: currentPageData + 1,
pageSize: pageSize,
sortByField: {
field: 'keyName',
direction: 'ASC'
}
},
categoryName: 'BOUNTY'
});
}, 100);
}
};
const handleRefetch = (): void => {
cleanDt();
setCurrentPageData(1);
setDefinitionBody({
pageInfo: {
currentPage: 1,
pageSize: pageSize,
sortByField: {
field: 'keyName',
direction: 'ASC'
}
},
categoryName: 'BOUNTY'
});
};
return (
<div data-testid="prim-tab">
<PageContainer
view="single"
left={<Filter />}
right={
<CfaDatatable
className={styles.datatable}
ref={dtref}
data-testid="datatable"
slot="single"
header={header}
body={body}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
data={dList.map((a: any) => ({ ...a })) ?? []}
onScrollEnd={getNextData}
loading={loading}
emptyWarning="Kayıt bulunamadı."
/>
}
/>
{isOpenDetailModal ? (
<div slot="single">
<EditModal
data-testid="edit-modal"
editData={editData}
setIsOpenDetailModal={setIsOpenDetailModal}
setEditData={setEditData}
refetchData={handleRefetch}
/>
</div>
) : null}
</div>
);
};
export default PrimTab;
my api code:
import { createApi, arkBaseQuery } from '@ark-technical-react-components/ark-react-state-management';
import { HttpMethod } from '@ark-technical-modules/ark-api-invoker';
import { getAppUrlForInvoker } from '@ark-technical-modules/ark-utils';
import { DefinitionBody, DefinitionList, ResultList } from '../models/DefinitionModel';
export const definitionApi = createApi({
baseQuery: arkBaseQuery,
endpoints: (builder) => ({
getDefinitionList: builder.query<DefinitionList, DefinitionBody>({
query: (body) => ({
method: HttpMethod.POST,
requestCallParam: {
url: getAppUrlForInvoker('getAllDefinitionPath'),
data: body
}
})
}),
getEditDefinition: builder.query<DefinitionList, DefinitionBody>({
query: (body) => ({
method: HttpMethod.POST,
requestCallParam: {
url:
getAppUrlForInvoker('getAllDefinitionPath') +
'?categoryName=' +
body.categoryName +
'&keyname=' +
body.keyName,
data: body
}
})
}),
putDefinition: builder.mutation<ResultList, ResultList>({
query: (body) => ({
method: HttpMethod.POST,
requestCallParam: {
url: getAppUrlForInvoker('putDefinitionPath'),
data: body
}
})
})
})
});
export const {
useGetDefinitionListQuery,
useLazyGetDefinitionListQuery,
useLazyGetEditDefinitionQuery,
useGetEditDefinitionQuery,
usePutDefinitionMutation
} = definitionApi;
my test code :
import { ArkInvoker, HttpMethod, InvokerCallResponse } from '@ark-technical-modules/ark-api-invoker';
import { screen, render, waitFor, fireEvent } from '@testing-library/react';
import { expect } from 'chai';
import { MemoryRouter } from 'react-router-dom';
import { Provider } from '@ark-technical-react-components/ark-react-state-management';
import { store } from '../../store';
import { resetStoreCache } from '../../../test/testUtils';
import PrimTab from './PrimTab';
import { CfaDatatable } from '@cfa-web-components/cfa-datatable/src/CfaDatatable';
import sinon from 'sinon';
import { getAppUrlForInvoker } from '@ark-technical-modules/ark-utils';
import { DefinitionBody, DefinitionList } from '../../store/models/DefinitionModel';
const arkInvoker = new ArkInvoker();
describe('PrimTab Component', () => {
const getDefinitionResponse: InvokerCallResponse<DefinitionList> = {
result: {
data: {
totalCount: 10,
resultList: [
{
companyId: 1,
categoryName: 'string',
keyName: 'string',
valueName: 'string',
explanationText: 'string',
isActive: 'string'
},
{
companyId: 1,
categoryName: 'string',
keyName: 'string',
valueName: 'string',
explanationText: 'string',
isActive: 'string'
},
{
companyId: 1,
categoryName: 'string',
keyName: 'string',
valueName: 'string',
explanationText: 'string',
isActive: 'string'
},
{
companyId: 1,
categoryName: 'string',
keyName: 'string',
valueName: 'string',
explanationText: 'string',
isActive: 'string'
},
{
companyId: 1,
categoryName: 'string',
keyName: 'string',
valueName: 'string',
explanationText: 'string',
isActive: 'string'
},
{
companyId: 1,
categoryName: 'string',
keyName: 'string',
valueName: 'string',
explanationText: 'string',
isActive: 'string'
}
]
},
config: {},
headers: {},
status: 200,
statusText: 'OK'
}
};
let invokerStub: sinon.SinonStub;
let getDefinitionListStub: sinon.SinonStub;
const setDlist = sinon.spy()
before(() => {
invokerStub = sinon.stub(arkInvoker, 'callMethod');
const payload: DefinitionBody = {
pageInfo: {
currentPage: 1,
pageSize: 5,
sortByField: {
field: 'keyName',
direction: 'ASC'
}
},
categoryName: 'BOUNTY'
};
// eslint-disable-next-line @typescript-eslint/no-unused-vars
getDefinitionListStub = invokerStub
.withArgs(
HttpMethod.POST,
sinon.match.has('url', getAppUrlForInvoker('getAllDefinitionPath')).and(sinon.match.has('data', payload))
)
.resolves(getDefinitionResponse);
});
beforeEach(() => {
resetStoreCache();
});
after(() => {
invokerStub.restore();
});
it('render component', () => {
render(
<Provider store={store}>
<MemoryRouter>
<PrimTab />
</MemoryRouter>
</Provider>
);
const primTab = screen.getByTestId('prim-tab');
expect(primTab).not.to.be.null;
});
it('test edit btn', async () => {
const refetchData = sinon.spy();
render(
<Provider store={store}>
<MemoryRouter>
<PrimTab />
</MemoryRouter>
</Provider>
);
const dList = getDefinitionResponse.result?.data.resultList;
const datatable = screen.getByTestId('datatable') as CfaDatatable;
datatable.data = dList?.map((a) => ({ ...a }));
await datatable.updateComplete;
const editBtn = datatable.shadowRoot?.querySelectorAll('[data-testid="edit-btn"]')[0] as HTMLButtonElement;
await waitFor(() => {
fireEvent.click(editBtn);
});
const saveBtn = screen.getByTestId('save-btn') as HTMLButtonElement;
await waitFor(() => {
fireEvent.click(saveBtn);
expect(refetchData.calledOnce)
})
});
it('test scroll datatable', async () => {
const getNextData = sinon.spy();
render(
<Provider store={store}>
<MemoryRouter>
<PrimTab />
</MemoryRouter>
</Provider>
);
const dList = getDefinitionResponse.result?.data.resultList;
const datatable = screen.getByTestId('datatable') as CfaDatatable;
datatable.data = dList?.map((a) => ({ ...a }) ?? []);
await datatable.updateComplete;
const scrollable = datatable.shadowRoot?.querySelector('.content') as HTMLDivElement;
await waitFor(() => {
fireEvent.scroll(scrollable, { target: { scrollY: 9999 } });
expect(getNextData.calledOnce)
});
});
});
my coverage table
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s |
---|---|---|---|---|---|
All files | 93.89 | 76.66 | 90 | 93.7 | |
src/common | 100 | 100 | 100 | 100 | |
Layout.support.tsx | 100 | 100 | 100 | 100 | |
src/components/common/edit-modal | 100 | 77.5 | 100 | 100 | |
EditModal.tsx | 100 | 77.5 | 100 | 100 | 47-65 |
src/components/common/filter | 100 | 100 | 100 | 100 | |
Filter.tsx | 100 | 100 | 100 | 100 | |
src/components/common/header | 100 | 100 | 100 | 100 | |
Header.tsx | 100 | 100 | 100 | 100 | |
src/components/common/logo | 100 | 100 | 100 | 100 | |
Logo.tsx | 100 | 100 | 100 | 100 | |
src/components/common/page-container | 100 | 100 | 100 | 100 | |
PageContainer.tsx | 100 | 100 | 100 | 100 | |
src/components/common/user-detail | 100 | 100 | 100 | 100 | |
UserDetail.tsx | 100 | 100 | 100 | 100 | |
src/pages/home-page | 100 | 83.33 | 100 | 100 | |
HomePage.tsx | 100 | 83.33 | 100 | 100 | 32 |
src/pages/not-found-page | 100 | 100 | 100 | 100 | |
NotFoundPage.tsx | 100 | 100 | 100 | 100 | |
src/pages/tabs | 87.23 | 68.75 | 78.57 | 86.95 | |
LiveTab.tsx | 100 | 100 | 100 | 100 | |
PrimTab.tsx | 86.04 | 68.75 | 75 | 85.71 | 105-109,154 |
ZamTab.tsx | 100 | 100 | 100 | 100 | |
src/store | 100 | 100 | 100 | 100 | |
store.ts | 100 | 100 | 100 | 100 | |
src/store/apis | 83.33 | 100 | 75 | 83.33 | |
definitionApi.ts | 83.33 | 100 | 75 | 83.33 | 19 |
src/store/slices | 87.5 | 100 | 50 | 87.5 | |
loadingSlice.tsx | 80 | 100 | 0 | 80 | 12 |
toastSlice.ts | 100 | 100 | 100 | 100 | |
test | 100 | 100 | 100 | 100 | |
testUtils.tsx | 100 | 100 | 100 | 100 | |
-------------------------------------- | --------- | ---------- | --------- | --------- | ------------------- |
Lines 105-109,154 are uncovered lines and these correspond to the following lines.
const getNextData = (): void => {
if (currentPageData < lastPage && !loading) {
dispatch(updateLoading(true));
setTimeout(() => {
setCurrentPageData(currentPageData + 1);
setDefinitionBody({
pageInfo: {
currentPage: currentPageData + 1,
pageSize: pageSize,
sortByField: {
field: 'keyName',
direction: 'ASC'
}
},
categoryName: 'BOUNTY'
});
}, 100);
}
};
// eslint-disable-next-line @typescript-eslint/no-explicit-any
data={dList.map((a: any) => ({ ...a })) ?? []}