Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add cell level to differential expression #940

Merged
66 changes: 66 additions & 0 deletions src/__test__/data/cell_level_cell_sets.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
[{
"key": "clm-0",
"name": "Some Cell Level Track",
"rootNode": true,
"children": [
{
"key": "clm-0-0",
"name": "Some cell level zero",
"color": "#8c564b",
"cellIds": [1, 21, 51, 86]
},
{
"key": "clm-0-1",
"name": "Some cell level one",
"color": "#d62728",
"cellIds": [11, 42, 96]
}
],
"type": "CLM"
},
{
"key": "clm-1",
"name": "Another Cell Level Track",
"rootNode": true,
"children": [
{
"key": "clm-1-0",
"name": "Another cell level zero",
"color": "#8c564b",
"cellIds": [2, 22, 52, 87]
},
{
"key": "clm-1-1",
"name": "Another cell level one",
"color": "#d62728",
"cellIds": [15, 47, 49]
}
],
"type": "CLM"
},
{
"key": "sample-clm-0",
"name": "Sample Cell Level Track",
"rootNode": true,
"children": [
{
"key": "sample-clm-0-0",
"name": "Sample cell level zero",
"color": "#8c564b",
"cellIds": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
11, 12, 13, 14, 15, 16, 17, 18, 19, 20,
21, 22, 23, 24, 25, 26, 27, 28, 29 , 30,
31, 32, 33, 34, 35, 36, 37, 38, 39, 40]
},
{
"key": "sample-clm-0-1",
"name": "Sample cell level one",
"color": "#d62728",
"cellIds": [41, 42, 43, 44, 45, 46, 47, 48, 49, 50,
51, 52, 53, 54, 55, 56, 57, 58, 59, 60,
61, 62, 63, 64, 65, 66, 67, 68, 69, 70,
71, 72, 73, 74, 75]
}
],
"type": "CLMPerSample"
}]
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,30 @@ import userEvent from '@testing-library/user-event';
import fetchMock, { enableFetchMocks } from 'jest-fetch-mock';

import { makeStore } from 'redux/store';
import mockAPI, { generateDefaultMockAPIResponses } from '__test__/test-utils/mockAPI';
import mockAPI, { generateDefaultMockAPIResponses, promiseResponse } from '__test__/test-utils/mockAPI';
import * as getBatchDiffExpr from 'utils/extraActionCreators/differentialExpression/getBatchDiffExpr';
import * as checkCanRunDiffExprModule from 'utils/extraActionCreators/differentialExpression/checkCanRunDiffExpr';
import mockLoader from 'components/Loader';

import cellSetsData from '__test__/data/cell_sets.json';
import cellLevelCellSets from '__test__/data/cell_level_cell_sets.json';

jest.spyOn(checkCanRunDiffExprModule, 'default').mockImplementation(() => 'TRUE');
jest.mock('utils/extraActionCreators/differentialExpression/getBatchDiffExpr');
jest.mock('components/Loader', () => jest.fn(() => <div data-testid='mockLoader'>Mock Loader</div>));

describe('Batch differential expression tests ', () => {
let storeState = null;
const mockApiResponses = _.merge(generateDefaultMockAPIResponses(fake.EXPERIMENT_ID));

const customCellSetsData = _.cloneDeep(cellSetsData);
// Add the cell level cell sets
customCellSetsData.cellSets.push(...cellLevelCellSets);

const mockApiResponses = {
...generateDefaultMockAPIResponses(fake.EXPERIMENT_ID),
[`experiments/${fake.EXPERIMENT_ID}/cellSets$`]: () => promiseResponse(JSON.stringify(customCellSetsData)),
};

let getBatchDiffExprSpy;

beforeEach(async () => {
Expand All @@ -35,6 +47,7 @@ describe('Batch differential expression tests ', () => {
storeState = makeStore();
getBatchDiffExprSpy = jest.spyOn(getBatchDiffExpr, 'default');
});

const secondOptionText = 'Compare between two selected samples/groups in a cell set for all cell sets';
const renderPage = async () => {
await act(async () => render(
Expand All @@ -52,23 +65,75 @@ describe('Batch differential expression tests ', () => {
it('Shows correct input fields for each comparison option', async () => {
await renderPage();

const compareForCellSetsRadio = screen.getByLabelText(secondOptionText);
const compareForSamplesRadio = screen.getByLabelText(/Compare two cell sets for all samples\/groups/i);
const compareBetweenSamplesRadio = screen.getByLabelText(secondOptionText);
const compareBetweenCellSetsRadio = screen.getByLabelText(/Compare two cell sets for all samples\/groups/i);

expect(screen.getByText(/Select the cell sets for which marker genes are to be computed in batch:/i)).toBeInTheDocument();
expect(screen.getByText('Select a cell set...')).toBeInTheDocument();

// Check compareForCellSetsRadio
await act(() => userEvent.click(compareForCellSetsRadio));
await act(() => userEvent.click(screen.getByText('Select a cell set...')));

const cellBasedClasses = [
'Fake louvain clusters',
'Custom cell sets',
'Some Cell Level Track',
'Another Cell Level Track'
];

const sampleBasedClasses = [
'Samples',
'Track_1',
'Sample Cell Level Track'
];

const sampleBasedSets = [
'KO', 'WT1', 'WT2', 'KMeta', 'WMetaT', 'Sample cell level zero', 'Sample cell level one',
];

// Shows the correct cell classes as options
cellBasedClasses.forEach((text) => {
expect(screen.getByText(text)).toBeInTheDocument();
})

// Doesn't show the samples or sample-based metadata cell classes as options
sampleBasedClasses.forEach((text) => {
expect(screen.queryByText(text)).not.toBeInTheDocument();
})

// Check compareBetweenSamplesRadio
await act(() => userEvent.click(compareBetweenSamplesRadio));
expect(screen.getByText(/Select the comparison sample\/groups for which batch/i)).toBeInTheDocument();
expect(screen.getByText(/In batch for each cell set in:/i)).toBeInTheDocument();
expect(screen.getByText(/Select a cell set.../i)).toBeInTheDocument();

// Check compareForSamplesRadio
await act(() => userEvent.click(compareForSamplesRadio));
expect(screen.getAllByText('Select a sample/group...')).toHaveLength(2);
screen.getAllByText('Select a sample/group...').forEach((match) => {
expect(match).toBeVisible();
});

await act(() => userEvent.click(screen.getAllByText('Select a sample/group...')[0]));

// Shows the sample options and their classes
[...sampleBasedSets, ...sampleBasedClasses].forEach((text) => {
expect(screen.getByText(text)).toBeInTheDocument();
});

// Check compareBetweenCellSetsRadio
await act(() => userEvent.click(compareBetweenCellSetsRadio));
expect(screen.getByText(/Select the comparison cell sets for which batch/i)).toBeInTheDocument();
expect(screen.getByText(/In batch for each sample\/group in:/i)).toBeInTheDocument();
expect(screen.getByText(/Select samples or metadata.../i)).toBeInTheDocument();

await act(() => userEvent.click(screen.getAllByText('Select a cell set...')[0]));


const someLouvainCellSets = Array.from({ length: 11 }, (x, index) => `Cluster ${index}`);
// Shows only some of the louvain options and not the others
// Because it is a virtual list which only renders the options that enter the
// options that fit in the display
[...someLouvainCellSets, 'fake louvain clusters'].forEach((text) => {
expect(screen.getByText(text)).toBeInTheDocument();
});
});

it('sending a request should work', async () => {
Expand Down Expand Up @@ -107,6 +172,7 @@ describe('Batch differential expression tests ', () => {
['louvain-0', 'louvain-1', 'louvain-2', 'louvain-3', 'louvain-4', 'louvain-5', 'louvain-6', 'louvain-7', 'louvain-8', 'louvain-9', 'louvain-10', 'louvain-11', 'louvain-12', 'louvain-13']);
});
});

it('shows the Loader while fetching data', async () => {
getBatchDiffExpr.mockImplementation(() => new Promise((resolve) => {
setTimeout(() => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ const DiffExprCompute = (props) => {
* Constructs a form item, a `Select` field with selectable clusters.
*/
const renderClusterSelectorItem = ({
title, option, filterType,
title, option, filterTypes,
}) => {
if (!cellSets.accessible) {
return (
Expand All @@ -113,7 +113,7 @@ const DiffExprCompute = (props) => {
<DiffExprSelect
title={title}
option={option}
filterType={filterType}
filterTypes={filterTypes}
onSelectCluster={onSelectCluster}
selectedComparison={comparisonGroup[selectedComparison]}
cellSets={cellSets}
Expand Down Expand Up @@ -234,40 +234,40 @@ const DiffExprCompute = (props) => {
renderClusterSelectorItem({
title: 'Compare cell set:',
option: 'cellSet',
filterType: 'cellSets',
filterTypes: ['cellSets', 'CLM'],
})
}

{renderClusterSelectorItem({
title: 'and cell set:',
option: 'compareWith',
filterType: 'cellSets',
filterTypes: ['cellSets', 'CLM'],
})}

{renderClusterSelectorItem({
title: 'within sample/group:',
option: 'basis',
filterType: 'metadataCategorical',
filterTypes: ['metadataCategorical', 'CLMPerSample'],
})}
</>
) : (
<>
{renderClusterSelectorItem({
title: 'Compare cell set:',
option: 'basis',
filterType: 'cellSets',
filterTypes: ['cellSets', 'CLM'],
})}

{renderClusterSelectorItem({
title: 'between sample/group:',
option: 'cellSet',
filterType: 'metadataCategorical',
filterTypes: ['metadataCategorical', 'CLMPerSample'],
})}

{renderClusterSelectorItem({
title: 'and sample/group:',
option: 'compareWith',
filterType: 'metadataCategorical',
filterTypes: ['metadataCategorical', 'CLMPerSample'],
})}
</>
)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,12 @@ const { Option, OptGroup } = Select;

const DiffExprSelect = (props) => {
const {
title, option, filterType, onSelectCluster, selectedComparison, cellSets, value
title, option, filterTypes, onSelectCluster, selectedComparison, cellSets, value
} = props;
// Depending on the cell set type specified, set the default name
const placeholder = filterType === 'metadataCategorical' ? 'sample/group' : 'cell set';
const placeholder = filterTypes.includes('metadataCategorical') ? 'sample/group' : 'cell set';
const { hierarchy, properties } = cellSets;
const tree = composeTree(hierarchy, properties, filterType);
const tree = composeTree(hierarchy, properties, filterTypes);

const renderChildren = (rootKey, children) => {
if (!children || children.length === 0) { return (<></>); }
Expand Down Expand Up @@ -96,7 +96,7 @@ DiffExprSelect.propTypes = {
selectedComparison: PropTypes.object.isRequired,
cellSets: PropTypes.object.isRequired,
option: PropTypes.string.isRequired,
filterType: PropTypes.string.isRequired,
filterTypes: PropTypes.array.isRequired,
onSelectCluster: PropTypes.func.isRequired,
};
export default DiffExprSelect;
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/* eslint-disable no-param-reassign */
import React, { useState } from 'react';
import {
Dropdown, Button, Tooltip,
Dropdown, Button,
} from 'antd';
import PropTypes from 'prop-types';

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,12 @@ const BatchDiffExpression = (props) => {
const cellSets = useSelector(getCellSets());
const { properties, hierarchy } = cellSets;
const experimentName = useSelector((state) => state.experimentSettings.info.experimentName);

const cellSetNodes = useSelector(getCellSetsHierarchyByType('cellSets'));
const clmNodes = useSelector(getCellSetsHierarchyByType('CLM'));

const metadataCellSetNodes = useSelector(getCellSetsHierarchyByType('metadataCategorical'));
const clmPerSampleNodes = useSelector(getCellSetsHierarchyByType('CLMPerSample'));

const [dataLoading, setDataLoading] = useState();

Expand All @@ -65,6 +69,16 @@ const BatchDiffExpression = (props) => {

const [sample] = useSelector(getCellSetsHierarchyByKeys(['sample']));

const cellDefinedNodes = useMemo(
() => [...cellSetNodes, ...clmNodes],
[cellSetNodes, clmNodes],
);

const sampleDefinedNodes = useMemo(
() => [...metadataCellSetNodes, ...clmPerSampleNodes],
[metadataCellSetNodes, clmPerSampleNodes],
);

const isDatasetUnisample = useMemo(() => sample?.children.length === 1, [sample]);
useEffect(() => {
dispatch(loadCellSets(experimentId));
Expand Down Expand Up @@ -182,7 +196,7 @@ const BatchDiffExpression = (props) => {
onChange={(value) => changeComparison({ basis: value })}
value={comparison.basis}
style={{ width: '40%' }}
options={getSelectOptions(cellSetNodes)}
options={getSelectOptions(cellDefinedNodes)}
/>
<br />
</>
Expand All @@ -196,7 +210,7 @@ const BatchDiffExpression = (props) => {
<DiffExprSelect
title='Compare sample/group:'
option='cellSet'
filterType='metadataCategorical'
filterTypes={['metadataCategorical', 'CLMPerSample']}
onSelectCluster={(cellSet) => changeComparison({ cellSet })}
selectedComparison={{ cellSet: comparison.cellSet }}
value={comparison.cellSet}
Expand All @@ -205,7 +219,7 @@ const BatchDiffExpression = (props) => {
<DiffExprSelect
title='To sample/group:'
option='compareWith'
filterType='metadataCategorical'
filterTypes={['metadataCategorical', 'CLMPerSample']}
onSelectCluster={(cellSet) => changeComparison({ compareWith: cellSet })}
selectedComparison={{ cellSet: comparison.cellSet }}
value={comparison.compareWith}
Expand All @@ -218,7 +232,7 @@ const BatchDiffExpression = (props) => {
onChange={(value) => changeComparison({ basis: value })}
value={comparison.basis}
style={{ width: '33.5%' }}
options={getSelectOptions(cellSetNodes)}
options={getSelectOptions(cellDefinedNodes)}
/>
</>
);
Expand All @@ -231,7 +245,7 @@ const BatchDiffExpression = (props) => {
<DiffExprSelect
title='Compare cell set:'
option='cellSet'
filterType='cellSets'
filterTypes={['cellSets', 'CLM']}
onSelectCluster={(cellSet) => changeComparison({ cellSet })}
selectedComparison={{ cellSet: comparison.cellSet }}
value={comparison.cellSet}
Expand All @@ -240,7 +254,7 @@ const BatchDiffExpression = (props) => {
<DiffExprSelect
title='To cell set:'
option='compareWith'
filterType='cellSets'
filterTypes={['cellSets', 'CLM']}
onSelectCluster={(cellSet) => changeComparison({ compareWith: cellSet })}
selectedComparison={{ cellSet: comparison.cellSet }}
value={comparison.compareWith}
Expand All @@ -253,7 +267,7 @@ const BatchDiffExpression = (props) => {
onChange={(value) => changeComparison({ basis: value })}
value={comparison.basis}
style={{ width: '34%' }}
options={getSelectOptions(metadataCellSetNodes)}
options={getSelectOptions(sampleDefinedNodes)}
/>
</>
);
Expand Down
Loading
Loading