mocha uncovered lines data loading problem

20 views Asked by At

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 })) ?? []}
0

There are 0 answers