Skip to content
Snippets Groups Projects
Commit 8d839a33 authored by mateusz-winiarczyk's avatar mateusz-winiarczyk
Browse files

feat(export): included compartment pathways and excluded compartment pathways

parent e0d62ca5
No related branches found
No related tags found
2 merge requests!223reset the pin numbers before search results are fetch (so the results will be...,!98feat(export): included compartment pathways and excluded compartment pathways
Showing
with 979 additions and 1 deletion
import { Types } from './Types';
import { useEffect } from 'react';
import { useAppSelector } from '@/redux/hooks/useAppSelector';
import { modelsDataSelector } from '@/redux/models/models.selectors';
import { useAppDispatch } from '@/redux/hooks/useAppDispatch';
import { getCompartmentPathways } from '@/redux/compartmentPathways/compartmentPathways.thunks';
import { Annotations } from '../Annotations';
import { Types } from './Types';
import { IncludedCompartmentPathways } from './IncludedCompartmentPathways ';
import { ExcludedCompartmentPathways } from './ExcludedCompartmentPathways';
import { Columns } from './Columns';
import { getModelsIds } from './Elements.utils';
export const Elements = (): React.ReactNode => {
const models = useAppSelector(modelsDataSelector);
const dispatch = useAppDispatch();
useEffect(() => {
const modelsIds = getModelsIds(models);
dispatch(getCompartmentPathways(modelsIds));
}, [dispatch, models]);
return (
<div data-testid="elements-tab">
<Types />
<Columns />
<Annotations />
<IncludedCompartmentPathways />
<ExcludedCompartmentPathways />
</div>
);
};
/* eslint-disable no-magic-numbers */
import { CompartmentPathwayDetails } from '@/types/models';
import { modelsFixture } from '@/models/fixtures/modelsFixture';
import { getCompartmentPathwaysCheckboxElements, getModelsIds } from './Elements.utils';
describe('getCompartmentPathwaysCheckboxElements', () => {
it('should return an empty array when given an empty items array', () => {
const items: CompartmentPathwayDetails[] = [];
const result = getCompartmentPathwaysCheckboxElements(items);
expect(result).toEqual([]);
});
it('should correctly extract unique names and corresponding ids from items', () => {
const items = [
{ id: 1, name: 'Compartment A' },
{ id: 2, name: 'Compartment B' },
{ id: 3, name: 'Compartment A' },
{ id: 4, name: 'Compartment C' },
] as CompartmentPathwayDetails[];
const result = getCompartmentPathwaysCheckboxElements(items);
expect(result).toEqual([
{ id: '1', label: 'Compartment A' },
{ id: '2', label: 'Compartment B' },
{ id: '4', label: 'Compartment C' },
]);
});
it('should correctly extract unique names and corresponding ids from items and sorts them alphabetically', () => {
const items = [
{ id: 1, name: 'Compartment C' },
{ id: 2, name: 'Compartment A' },
{ id: 3, name: 'Compartment B' },
{ id: 4, name: 'Compartment A' },
{ id: 5, name: 'Compartment D' },
] as CompartmentPathwayDetails[];
const result = getCompartmentPathwaysCheckboxElements(items);
expect(result).toEqual([
{ id: '2', label: 'Compartment A' },
{ id: '3', label: 'Compartment B' },
{ id: '1', label: 'Compartment C' },
{ id: '5', label: 'Compartment D' },
]);
});
});
const MODELS_IDS = modelsFixture.map(item => item.idObject);
describe('getModelsIds', () => {
it('should return an empty array if models are not provided', () => {
const result = getModelsIds(undefined);
expect(result).toEqual([]);
});
it('should return an array of model IDs', () => {
const result = getModelsIds(modelsFixture);
expect(result).toEqual(MODELS_IDS);
});
});
/* eslint-disable no-magic-numbers */
import { CompartmentPathwayDetails, MapModel } from '@/types/models';
type AddedNames = { [key: string]: number };
type CheckboxElement = { id: string; label: string };
type CheckboxElements = CheckboxElement[];
export const getCompartmentPathwaysCheckboxElements = (
items: CompartmentPathwayDetails[],
): CheckboxElements => {
const addedNames: AddedNames = {};
const setNameToIdIfUndefined = (item: CompartmentPathwayDetails): void => {
if (addedNames[item.name] === undefined) {
addedNames[item.name] = item.id;
}
};
items.forEach(setNameToIdIfUndefined);
const parseIdAndLabel = ([name, id]: [name: string, id: number]): CheckboxElement => ({
id: id.toString(),
label: name,
});
const sortByLabel = (a: CheckboxElement, b: CheckboxElement): number => {
if (a.label > b.label) return 1;
return -1;
};
const elements = Object.entries(addedNames).map(parseIdAndLabel).sort(sortByLabel);
return elements;
};
export const getModelsIds = (models: MapModel[] | undefined): number[] => {
if (!models) return [];
return models.map(model => model.idObject);
};
/* eslint-disable no-magic-numbers */
import { render, screen, waitFor } from '@testing-library/react';
import {
InitialStoreState,
getReduxWrapperWithStore,
} from '@/utils/testing/getReduxWrapperWithStore';
import { StoreType } from '@/redux/store';
import { act } from 'react-dom/test-utils';
import { compartmentPathwaysDetailsFixture } from '@/models/fixtures/compartmentPathways';
import { ExcludedCompartmentPathways } from './ExcludedCompartmentPathways.component';
const renderComponent = (initialStoreState: InitialStoreState = {}): { store: StoreType } => {
const { Wrapper, store } = getReduxWrapperWithStore(initialStoreState);
return (
render(
<Wrapper>
<ExcludedCompartmentPathways />
</Wrapper>,
),
{
store,
}
);
};
const CHECKBOX_ELEMENT_NAME = compartmentPathwaysDetailsFixture[0].name;
describe('ExcludedCompartmentPathways - component', () => {
it('should display compartment / pathways checkboxes when fetching data is successful', async () => {
renderComponent({
compartmentPathways: {
data: compartmentPathwaysDetailsFixture,
loading: 'succeeded',
error: {
message: '',
name: '',
},
},
});
expect(screen.queryByTestId('checkbox-filter')).not.toBeVisible();
const navigationButton = screen.getByTestId('accordion-item-button');
act(() => {
navigationButton.click();
});
expect(screen.getByText('Select excluded compartment / pathways')).toBeInTheDocument();
await waitFor(() => {
expect(screen.getByTestId('checkbox-filter')).toBeInTheDocument();
expect(screen.getByLabelText('search-input')).toBeInTheDocument();
expect(screen.getByLabelText(CHECKBOX_ELEMENT_NAME)).toBeInTheDocument();
});
});
it('should not display compartment / pathways checkboxes when fetching data fails', async () => {
renderComponent({
compartmentPathways: {
data: [],
loading: 'failed',
error: {
message: '',
name: '',
},
},
});
expect(screen.getByText('Select excluded compartment / pathways')).toBeInTheDocument();
const navigationButton = screen.getByTestId('accordion-item-button');
act(() => {
navigationButton.click();
});
expect(screen.queryByTestId('checkbox-filter')).not.toBeInTheDocument();
});
it('should not display compartment / pathways checkboxes when fetched data is empty', async () => {
renderComponent({
compartmentPathways: {
data: [],
loading: 'succeeded',
error: {
message: '',
name: '',
},
},
});
expect(screen.getByText('Select excluded compartment / pathways')).toBeInTheDocument();
const navigationButton = screen.getByTestId('accordion-item-button');
act(() => {
navigationButton.click();
});
expect(screen.queryByTestId('checkbox-filter')).not.toBeInTheDocument();
});
it('should display loading message when fetching data is pending', async () => {
renderComponent({
compartmentPathways: {
data: [],
loading: 'pending',
error: {
message: '',
name: '',
},
},
});
expect(screen.getByText('Select excluded compartment / pathways')).toBeInTheDocument();
const navigationButton = screen.getByTestId('accordion-item-button');
act(() => {
navigationButton.click();
});
expect(screen.getByText('Loading...')).toBeInTheDocument();
});
});
/* eslint-disable no-magic-numbers */
import { useAppSelector } from '@/redux/hooks/useAppSelector';
import {
compartmentPathwaysDataSelector,
loadingCompartmentPathwaysSelector,
} from '@/redux/compartmentPathways/compartmentPathways.selectors';
import { CheckboxFilter } from '../../CheckboxFilter';
import { CollapsibleSection } from '../../CollapsibleSection';
import { getCompartmentPathwaysCheckboxElements } from '../Elements.utils';
export const ExcludedCompartmentPathways = (): React.ReactNode => {
const loadingCompartmentPathways = useAppSelector(loadingCompartmentPathwaysSelector);
const isPending = loadingCompartmentPathways === 'pending';
const compartmentPathways = useAppSelector(compartmentPathwaysDataSelector);
const checkboxElements = getCompartmentPathwaysCheckboxElements(compartmentPathways);
const isCheckboxFilterVisible = !isPending && checkboxElements && checkboxElements.length > 0;
return (
<CollapsibleSection title="Select excluded compartment / pathways">
{isPending && <p>Loading...</p>}
{isCheckboxFilterVisible && <CheckboxFilter options={checkboxElements} />}
</CollapsibleSection>
);
};
export { ExcludedCompartmentPathways } from './ExcludedCompartmentPathways.component';
/* eslint-disable no-magic-numbers */
import { render, screen, waitFor } from '@testing-library/react';
import {
InitialStoreState,
getReduxWrapperWithStore,
} from '@/utils/testing/getReduxWrapperWithStore';
import { StoreType } from '@/redux/store';
import { act } from 'react-dom/test-utils';
import { compartmentPathwaysDetailsFixture } from '@/models/fixtures/compartmentPathways';
import { IncludedCompartmentPathways } from './IncludedCompartmentPathways.component';
const renderComponent = (initialStoreState: InitialStoreState = {}): { store: StoreType } => {
const { Wrapper, store } = getReduxWrapperWithStore(initialStoreState);
return (
render(
<Wrapper>
<IncludedCompartmentPathways />
</Wrapper>,
),
{
store,
}
);
};
const CHECKBOX_ELEMENT_NAME = compartmentPathwaysDetailsFixture[0].name;
describe('IncludedCompartmentPathways - component', () => {
it('should display compartment / pathways checkboxes when fetching data is successful', async () => {
renderComponent({
compartmentPathways: {
data: compartmentPathwaysDetailsFixture,
loading: 'succeeded',
error: {
message: '',
name: '',
},
},
});
expect(screen.queryByTestId('checkbox-filter')).not.toBeVisible();
const navigationButton = screen.getByTestId('accordion-item-button');
act(() => {
navigationButton.click();
});
expect(screen.getByText('Select included compartment / pathways')).toBeInTheDocument();
await waitFor(() => {
expect(screen.getByTestId('checkbox-filter')).toBeInTheDocument();
expect(screen.getByLabelText('search-input')).toBeInTheDocument();
expect(screen.getByLabelText(CHECKBOX_ELEMENT_NAME)).toBeInTheDocument();
});
});
it('should not display compartment / pathways checkboxes when fetching data fails', async () => {
renderComponent({
compartmentPathways: {
data: [],
loading: 'failed',
error: {
message: '',
name: '',
},
},
});
expect(screen.getByText('Select included compartment / pathways')).toBeInTheDocument();
const navigationButton = screen.getByTestId('accordion-item-button');
act(() => {
navigationButton.click();
});
expect(screen.queryByTestId('checkbox-filter')).not.toBeInTheDocument();
});
it('should not display compartment / pathways checkboxes when fetched data is empty', async () => {
renderComponent({
compartmentPathways: {
data: [],
loading: 'succeeded',
error: {
message: '',
name: '',
},
},
});
expect(screen.getByText('Select included compartment / pathways')).toBeInTheDocument();
const navigationButton = screen.getByTestId('accordion-item-button');
act(() => {
navigationButton.click();
});
expect(screen.queryByTestId('checkbox-filter')).not.toBeInTheDocument();
});
it('should display loading message when fetching data is pending', async () => {
renderComponent({
compartmentPathways: {
data: [],
loading: 'pending',
error: {
message: '',
name: '',
},
},
});
expect(screen.getByText('Select included compartment / pathways')).toBeInTheDocument();
const navigationButton = screen.getByTestId('accordion-item-button');
act(() => {
navigationButton.click();
});
expect(screen.getByText('Loading...')).toBeInTheDocument();
});
});
/* eslint-disable no-magic-numbers */
import {
compartmentPathwaysDataSelector,
loadingCompartmentPathwaysSelector,
} from '@/redux/compartmentPathways/compartmentPathways.selectors';
import { useAppSelector } from '@/redux/hooks/useAppSelector';
import { CheckboxFilter } from '../../CheckboxFilter';
import { CollapsibleSection } from '../../CollapsibleSection';
import { getCompartmentPathwaysCheckboxElements } from '../Elements.utils';
export const IncludedCompartmentPathways = (): React.ReactNode => {
const loadingCompartmentPathways = useAppSelector(loadingCompartmentPathwaysSelector);
const isPending = loadingCompartmentPathways === 'pending';
const compartmentPathways = useAppSelector(compartmentPathwaysDataSelector);
const checkboxElements = getCompartmentPathwaysCheckboxElements(compartmentPathways);
return (
<CollapsibleSection title="Select included compartment / pathways">
{isPending && <p>Loading...</p>}
{!isPending && checkboxElements && checkboxElements.length > 0 && (
<CheckboxFilter options={checkboxElements} />
)}
</CollapsibleSection>
);
};
export { IncludedCompartmentPathways } from './IncludedCompartmentPathways.component';
import { z } from 'zod';
export const compartmentPathwaySchema = z.object({
id: z.number(),
});
export const boundsSchema = z.object({
height: z.number(),
width: z.number(),
x: z.number(),
y: z.number(),
z: z.number(),
});
export const otherSchema = z.object({
modifications: z.array(z.unknown()),
structuralState: z.null(),
structures: z.object({}),
});
export const compartmentPathwayDetailsSchema = z.object({
abbreviation: z.null(),
activity: z.null(),
boundaryCondition: z.null(),
bounds: boundsSchema,
compartmentId: z.number().nullable(),
complexId: z.null(),
constant: z.null(),
elementId: z.string(),
formerSymbols: z.array(z.unknown()),
formula: z.null(),
fullName: z.string().nullable(),
glyph: z.any(),
hierarchyVisibilityLevel: z.string(),
homomultimer: z.null(),
hypothetical: z.null(),
id: z.number(),
initialAmount: z.null(),
initialConcentration: z.null(),
linkedSubmodel: z.null(),
modelId: z.number(),
name: z.string(),
notes: z.string(),
other: otherSchema,
references: z.array(z.unknown()),
symbol: z.null(),
synonyms: z.array(z.unknown()),
transparencyLevel: z.string(),
type: z.string(),
});
import { ZOD_SEED } from '@/constants';
// eslint-disable-next-line import/no-extraneous-dependencies
import { createFixture } from 'zod-fixture';
import { z } from 'zod';
import {
compartmentPathwayDetailsSchema,
compartmentPathwaySchema,
} from '../compartmentPathwaySchema';
export const compartmentPathwaysFixture = createFixture(z.array(compartmentPathwaySchema), {
seed: ZOD_SEED,
array: { min: 3, max: 3 },
});
export const compartmentPathwaysOverLimitFixture = createFixture(
z.array(compartmentPathwaySchema),
{
seed: ZOD_SEED,
array: { min: 101, max: 101 },
},
);
export const compartmentPathwaysDetailsFixture = createFixture(
z.array(compartmentPathwayDetailsSchema),
{
seed: ZOD_SEED,
array: { min: 3, max: 3 },
},
);
......@@ -39,4 +39,9 @@ export const apiPath = {
createOverlayFile: (): string => `files/`,
uploadOverlayFileContent: (fileId: number): string => `files/${fileId}:uploadContent`,
getStatisticsById: (projectId: string): string => `projects/${projectId}/statistics/`,
getCompartmentPathwaysIds: (objectId: number): string =>
`projects/${PROJECT_ID}/models/${objectId}/bioEntities/elements/?columns=id&type=Compartment,Pathway`,
getCompartmentPathwayDetails: (ids: number[]): string =>
`projects/${PROJECT_ID}/models/*/bioEntities/elements/?id=${ids.join(',')}`,
sendCompartmentPathwaysIds: (): string => `projects/${PROJECT_ID}/models/*/bioEntities/elements/`,
};
export const MAX_NUMBER_OF_IDS_IN_GET_QUERY = 100;
import { MapModel } from '@/types/models';
import { CompartmentPathwaysState } from './compartmentPathways.types';
export const COMPARTMENT_PATHWAYS_INITIAL_STATE_MOCK: CompartmentPathwaysState = {
loading: 'idle',
data: [],
error: { name: '', message: '' },
};
export const MODELS_MOCK: MapModel[] = [
{
idObject: 5053,
width: 26779.25,
height: 13503.0,
defaultCenterX: null,
defaultCenterY: null,
description: '',
name: 'Core PD map',
defaultZoomLevel: null,
tileSize: 256,
references: [],
authors: [],
creationDate: null,
modificationDates: [],
minZoom: 2,
maxZoom: 9,
},
{
idObject: 5054,
width: 26779.25,
height: 13503.0,
defaultCenterX: null,
defaultCenterY: null,
description: '',
name: 'Core PD map',
defaultZoomLevel: null,
tileSize: 256,
references: [],
authors: [],
creationDate: null,
modificationDates: [],
minZoom: 2,
maxZoom: 9,
},
];
export const MODELS_MOCK_SHORT: MapModel[] = [
{
idObject: 5050,
width: 26779.25,
height: 13503.0,
defaultCenterX: null,
defaultCenterY: null,
description: '',
name: 'Core PD map',
defaultZoomLevel: null,
tileSize: 256,
references: [],
authors: [],
creationDate: null,
modificationDates: [],
minZoom: 2,
maxZoom: 9,
},
];
/* eslint-disable no-magic-numbers */
import {
ToolkitStoreWithSingleSlice,
createStoreInstanceUsingSliceReducer,
} from '@/utils/createStoreInstanceUsingSliceReducer';
import { mockNetworkResponse } from '@/utils/mockNetworkResponse';
import { HttpStatusCode } from 'axios';
import {
compartmentPathwaysDetailsFixture,
compartmentPathwaysFixture,
compartmentPathwaysOverLimitFixture,
} from '@/models/fixtures/compartmentPathways';
import { getModelsIds } from '@/components/Map/Drawer/ExportDrawer/Elements/Elements.utils';
import { apiPath } from '../apiPath';
import compartmentPathwaysReducer from './compartmentPathways.slice';
import { CompartmentPathwaysState } from './compartmentPathways.types';
import { getCompartmentPathways } from './compartmentPathways.thunks';
import { MODELS_MOCK } from './compartmentPathways.mock';
const mockedAxiosClient = mockNetworkResponse();
const MODELS_MOCK_IDS = getModelsIds(MODELS_MOCK);
const INITIAL_STATE: CompartmentPathwaysState = {
loading: 'idle',
error: { name: '', message: '' },
data: [],
};
describe('compartmentPathways reducer', () => {
let store = {} as ToolkitStoreWithSingleSlice<CompartmentPathwaysState>;
beforeEach(() => {
store = createStoreInstanceUsingSliceReducer('compartmentPathways', compartmentPathwaysReducer);
});
it('should match initial state', () => {
const action = { type: 'unknown' };
expect(compartmentPathwaysReducer(undefined, action)).toEqual(INITIAL_STATE);
});
it('should update store on loading getCompartmentPathways query', async () => {
mockedAxiosClient
.onGet(apiPath.getCompartmentPathwaysIds(52))
.reply(HttpStatusCode.Ok, compartmentPathwaysFixture);
mockedAxiosClient
.onGet(apiPath.getCompartmentPathwayDetails([1, 2, 3]))
.reply(HttpStatusCode.Ok, compartmentPathwaysDetailsFixture);
mockedAxiosClient
.onPost(apiPath.sendCompartmentPathwaysIds())
.reply(HttpStatusCode.Ok, compartmentPathwaysDetailsFixture);
const { loading, data } = store.getState().compartmentPathways;
expect(loading).toEqual('idle');
expect(data).toEqual([]);
store.dispatch(getCompartmentPathways());
const { loading: loadingPending, data: dataPending } = store.getState().compartmentPathways;
expect(loadingPending).toEqual('pending');
expect(dataPending).toEqual([]);
});
it('should update store after succesful getCompartmentPathways query', async () => {
mockedAxiosClient
.onGet(apiPath.getCompartmentPathwaysIds(5053))
.reply(HttpStatusCode.Ok, compartmentPathwaysFixture);
mockedAxiosClient
.onGet(apiPath.getCompartmentPathwaysIds(5054))
.reply(HttpStatusCode.Ok, compartmentPathwaysOverLimitFixture);
const ids = compartmentPathwaysFixture.map(el => el.id);
mockedAxiosClient
.onGet(apiPath.getCompartmentPathwayDetails(ids))
.reply(HttpStatusCode.Ok, compartmentPathwaysDetailsFixture);
mockedAxiosClient
.onPost(apiPath.sendCompartmentPathwaysIds())
.reply(HttpStatusCode.Ok, compartmentPathwaysDetailsFixture);
const compartmentPathwaysPromise = store.dispatch(getCompartmentPathways(MODELS_MOCK_IDS));
const { loading, data } = store.getState().compartmentPathways;
expect(loading).toEqual('pending');
expect(data).toEqual([]);
const { type } = await compartmentPathwaysPromise;
expect(type).toBe('compartmentPathways/getCompartmentPathways/fulfilled');
const { loading: promiseFulfilled, data: dataFulfilled } = store.getState().compartmentPathways;
expect(dataFulfilled).toEqual([
...compartmentPathwaysDetailsFixture,
...compartmentPathwaysDetailsFixture,
]);
expect(promiseFulfilled).toEqual('succeeded');
});
it('should update store after failed getCompartmentPathways query', async () => {
mockedAxiosClient
.onGet(apiPath.getCompartmentPathwaysIds(5053))
.reply(HttpStatusCode.NotFound, []);
mockedAxiosClient
.onGet(apiPath.getCompartmentPathwayDetails([]))
.reply(HttpStatusCode.NotFound, []);
mockedAxiosClient
.onPost(apiPath.sendCompartmentPathwaysIds())
.reply(HttpStatusCode.NotFound, []);
const compartmentPathwaysPromise = store.dispatch(getCompartmentPathways(MODELS_MOCK_IDS));
const { loading, data } = store.getState().compartmentPathways;
expect(loading).toEqual('pending');
expect(data).toEqual([]);
await compartmentPathwaysPromise;
const { loading: promiseFulfilled, data: dataFulfilled } = store.getState().compartmentPathways;
expect(promiseFulfilled).toEqual('failed');
expect(dataFulfilled).toEqual([]);
});
});
import { ActionReducerMapBuilder } from '@reduxjs/toolkit';
import { getCompartmentPathways } from './compartmentPathways.thunks';
import { CompartmentPathwaysState } from './compartmentPathways.types';
export const getCompartmentPathwaysReducer = (
builder: ActionReducerMapBuilder<CompartmentPathwaysState>,
): void => {
builder
.addCase(getCompartmentPathways.pending, state => {
state.loading = 'pending';
})
.addCase(getCompartmentPathways.fulfilled, (state, action) => {
state.data = action.payload;
state.loading = 'succeeded';
})
.addCase(getCompartmentPathways.rejected, state => {
state.loading = 'failed';
// TODO: error management to be discussed in the team
});
};
import { rootSelector } from '@/redux/root/root.selectors';
import { createSelector } from '@reduxjs/toolkit';
export const compartmentPathwaysSelector = createSelector(
rootSelector,
state => state.compartmentPathways,
);
export const compartmentPathwaysDataSelector = createSelector(
compartmentPathwaysSelector,
state => state.data,
);
export const loadingCompartmentPathwaysSelector = createSelector(
compartmentPathwaysSelector,
state => state.loading,
);
import { createSlice } from '@reduxjs/toolkit';
import { CompartmentPathwaysState } from './compartmentPathways.types';
import { getCompartmentPathwaysReducer } from './compartmentPathways.reducers';
export const initialState: CompartmentPathwaysState = {
loading: 'idle',
error: { name: '', message: '' },
data: [],
};
export const compartmentPathwaysSlice = createSlice({
name: 'compartmentPathways',
initialState,
reducers: {},
extraReducers: builder => {
getCompartmentPathwaysReducer(builder);
},
});
export default compartmentPathwaysSlice.reducer;
/* eslint-disable no-magic-numbers */
import {
ToolkitStoreWithSingleSlice,
createStoreInstanceUsingSliceReducer,
} from '@/utils/createStoreInstanceUsingSliceReducer';
import { mockNetworkResponse } from '@/utils/mockNetworkResponse';
import { HttpStatusCode } from 'axios';
import {
compartmentPathwaysDetailsFixture,
compartmentPathwaysFixture,
compartmentPathwaysOverLimitFixture,
} from '@/models/fixtures/compartmentPathways';
import { getModelsIds } from '@/components/Map/Drawer/ExportDrawer/Elements/Elements.utils';
import { apiPath } from '../apiPath';
import compartmentPathwaysReducer from './compartmentPathways.slice';
import { CompartmentPathwaysState } from './compartmentPathways.types';
import { getCompartmentPathways } from './compartmentPathways.thunks';
import { MODELS_MOCK, MODELS_MOCK_SHORT } from './compartmentPathways.mock';
const mockedAxiosClient = mockNetworkResponse();
const MODELS_MOCK_IDS = getModelsIds(MODELS_MOCK);
describe('compartmentPathways thunk', () => {
let store = {} as ToolkitStoreWithSingleSlice<CompartmentPathwaysState>;
beforeEach(() => {
store = createStoreInstanceUsingSliceReducer('compartmentPathways', compartmentPathwaysReducer);
});
it('should handle query getCompartmentPathways properly when models are undefined', async () => {
mockedAxiosClient
.onGet(apiPath.getCompartmentPathwaysIds(52))
.reply(HttpStatusCode.Ok, compartmentPathwaysFixture);
mockedAxiosClient
.onGet(apiPath.getCompartmentPathwayDetails([1, 2, 3]))
.reply(HttpStatusCode.Ok, compartmentPathwaysDetailsFixture);
mockedAxiosClient
.onPost(apiPath.sendCompartmentPathwaysIds())
.reply(HttpStatusCode.Ok, compartmentPathwaysDetailsFixture);
const { loading, data } = store.getState().compartmentPathways;
expect(loading).toEqual('idle');
expect(data).toEqual([]);
const comparmentPathwaysPromise = store.dispatch(getCompartmentPathways());
const { loading: loadingPending, data: dataPending } = store.getState().compartmentPathways;
expect(loadingPending).toEqual('pending');
expect(dataPending).toEqual([]);
await comparmentPathwaysPromise;
const { loading: loadingFulfilled, data: dataFulfilled } = store.getState().compartmentPathways;
expect(loadingFulfilled).toEqual('succeeded');
expect(dataFulfilled).toEqual([]);
});
it('should handle sendCompartmentPathwaysIds request properly if it is more than 100 ids', async () => {
mockedAxiosClient
.onGet(apiPath.getCompartmentPathwaysIds(5053))
.reply(HttpStatusCode.Ok, compartmentPathwaysFixture);
mockedAxiosClient
.onGet(apiPath.getCompartmentPathwaysIds(5054))
.reply(HttpStatusCode.Ok, compartmentPathwaysOverLimitFixture);
const ids = compartmentPathwaysFixture.map(el => el.id);
mockedAxiosClient
.onGet(apiPath.getCompartmentPathwayDetails(ids))
.reply(HttpStatusCode.Ok, compartmentPathwaysDetailsFixture);
mockedAxiosClient
.onPost(apiPath.sendCompartmentPathwaysIds())
.reply(HttpStatusCode.Ok, compartmentPathwaysDetailsFixture);
const compartmentPathwaysPromise = store.dispatch(getCompartmentPathways(MODELS_MOCK_IDS));
const { loading, data } = store.getState().compartmentPathways;
expect(loading).toEqual('pending');
expect(data).toEqual([]);
const { type } = await compartmentPathwaysPromise;
expect(type).toBe('compartmentPathways/getCompartmentPathways/fulfilled');
const { loading: promiseFulfilled, data: dataFulfilled } = store.getState().compartmentPathways;
expect(dataFulfilled).toEqual([
...compartmentPathwaysDetailsFixture,
...compartmentPathwaysDetailsFixture,
]);
expect(promiseFulfilled).toEqual('succeeded');
});
it('should not do a network request sendCompartmentPathwaysIds if it is less than 100 ids', async () => {
const ONE_MODEL = MODELS_MOCK_SHORT[0];
const ID = ONE_MODEL.idObject;
mockedAxiosClient
.onGet(apiPath.getCompartmentPathwaysIds(ID))
.reply(HttpStatusCode.Ok, compartmentPathwaysFixture);
const ids = compartmentPathwaysFixture.map(el => el.id);
mockedAxiosClient
.onGet(apiPath.getCompartmentPathwayDetails(ids))
.reply(HttpStatusCode.Ok, compartmentPathwaysDetailsFixture);
mockedAxiosClient
.onPost(apiPath.sendCompartmentPathwaysIds())
.reply(HttpStatusCode.Ok, compartmentPathwaysDetailsFixture);
const compartmentPathwaysPromise = store.dispatch(getCompartmentPathways([ONE_MODEL.idObject]));
const { loading, data } = store.getState().compartmentPathways;
expect(loading).toEqual('pending');
expect(data).toEqual([]);
const { type } = await compartmentPathwaysPromise;
expect(type).toBe('compartmentPathways/getCompartmentPathways/fulfilled');
const { loading: promiseFulfilled, data: dataFulfilled } = store.getState().compartmentPathways;
expect(dataFulfilled).toEqual(compartmentPathwaysDetailsFixture);
expect(promiseFulfilled).toEqual('succeeded');
});
});
/* eslint-disable no-restricted-syntax */
import { axiosInstance } from '@/services/api/utils/axiosInstance';
import { createAsyncThunk } from '@reduxjs/toolkit';
import { validateDataUsingZodSchema } from '@/utils/validateDataUsingZodSchema';
import { CompartmentPathway, CompartmentPathwayDetails } from '@/types/models';
import {
compartmentPathwayDetailsSchema,
compartmentPathwaySchema,
} from '@/models/compartmentPathwaySchema';
import { z } from 'zod';
import { MAX_NUMBER_OF_IDS_IN_GET_QUERY } from './comparmentPathways.constants';
import { apiPath } from '../apiPath';
/** UTILS */
const fetchCompartmentPathwaysIds = async (
modelsIds: number[] | undefined,
): Promise<number[][]> => {
if (!modelsIds) return [];
const compartmentIds = [];
for (const modelId of modelsIds) {
/* eslint-disable no-await-in-loop */
const response = await axiosInstance<CompartmentPathway[]>(
apiPath.getCompartmentPathwaysIds(modelId),
);
const isDataValid = validateDataUsingZodSchema(
response.data,
z.array(compartmentPathwaySchema),
);
if (isDataValid) {
const result = response.data;
const ids: number[] = [];
result.forEach(item => {
ids.push(item.id);
});
compartmentIds.push(ids);
}
}
return compartmentIds;
};
const fetchCompartmentPathwayDetailsByPost = async (
compartmentPathwayIds: number[],
): Promise<CompartmentPathwayDetails[]> => {
const params = {
id: compartmentPathwayIds.join(','),
};
const body = new URLSearchParams(params);
const response = await axiosInstance.post<CompartmentPathwayDetails[]>(
apiPath.sendCompartmentPathwaysIds(),
body,
);
return response.data;
};
const fetchCompartmentPathwayDetailsByGet = async (
compartmentPathwayIds: number[],
): Promise<CompartmentPathwayDetails[]> => {
const response = await axiosInstance.get<CompartmentPathwayDetails[]>(
apiPath.getCompartmentPathwayDetails(compartmentPathwayIds),
);
return response.data;
};
const fetchCompartmentPathwayDetails = async (
compartmentPathwayIds: number[],
): Promise<CompartmentPathwayDetails[]> => {
if (compartmentPathwayIds.length) {
let compartmentPathwayDetails;
if (compartmentPathwayIds.length > MAX_NUMBER_OF_IDS_IN_GET_QUERY) {
compartmentPathwayDetails = await fetchCompartmentPathwayDetailsByPost(compartmentPathwayIds);
} else {
compartmentPathwayDetails = await fetchCompartmentPathwayDetailsByGet(compartmentPathwayIds);
}
const isDataValid = validateDataUsingZodSchema(
compartmentPathwayDetails,
z.array(compartmentPathwayDetailsSchema),
);
if (isDataValid) return compartmentPathwayDetails;
}
return [];
};
export const fetchCompartmentPathways = async (
compartmentPathwaysData: number[][],
): Promise<CompartmentPathwayDetails[]> => {
const compartments = [];
/* eslint-disable no-await-in-loop */
for (const compartmentPathwayIds of compartmentPathwaysData) {
const compartmentPathwayDetails = await fetchCompartmentPathwayDetails(compartmentPathwayIds);
if (compartmentPathwayDetails) compartments.push(...compartmentPathwayDetails);
}
return compartments;
};
/** UTILS */
export const getCompartmentPathways = createAsyncThunk(
'compartmentPathways/getCompartmentPathways',
async (modelsIds: number[] | undefined) => {
const compartmentIds = await fetchCompartmentPathwaysIds(modelsIds);
const comparmentPathways = await fetchCompartmentPathways(compartmentIds);
return comparmentPathways;
},
);
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment