Skip to content

Commit

Permalink
chore: added tests for ordering
Browse files Browse the repository at this point in the history
  • Loading branch information
nicoalee committed Jan 6, 2025
1 parent 675262a commit 7a84982
Show file tree
Hide file tree
Showing 6 changed files with 912 additions and 79 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import NeurosynthBreadcrumbs from 'components/NeurosynthBreadcrumbs';
import StateHandlerComponent from 'components/StateHandlerComponent/StateHandlerComponent';
import TextEdit from 'components/TextEdit/TextEdit';
import { useGetMetaAnalysisById, useGetMetaAnalysisResultById } from 'hooks';
import useGetSpecificationById from 'hooks/metaAnalyses/useGetSpecificationById';
import useUpdateMetaAnalysis from 'hooks/metaAnalyses/useUpdateMetaAnalysis';
import useUserCanEdit from 'hooks/useUserCanEdit';
import { ResultReturn, SpecificationReturn, StudysetReturn } from 'neurosynth-compose-typescript-sdk';
Expand Down Expand Up @@ -47,11 +46,8 @@ const MetaAnalysisPage: React.FC = () => {
: undefined
);

const { data: specification } = useGetSpecificationById(
(metaAnalysis?.specification as SpecificationReturn | undefined)?.id
);

// get request is set to nested: true so below casting is safe
const specification = metaAnalysis?.specification as SpecificationReturn;
const studyset = metaAnalysis?.studyset as StudysetReturn;
const annotation = metaAnalysis?.annotation as NeurostoreAnnotation;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,80 +1,88 @@
// this is organized as an array to make it orderable. Order is obtained from: https://nimare.readthedocs.io/en/stable/outputs.html#file-names
export const NimareOutputs = [
// possible value types
{ type: 'z', isValueType: true, description: 'Z-statistic' },
{ type: 't', isValueType: true, description: 'T-statistic' },
{ type: 'p', isValueType: true, description: 'p-value' },
{ type: 'logp', isValueType: true, description: 'Negative base-ten logarithm of p-value' },
{ type: 'chi2', isValueType: true, description: 'Chi-squared value' },
{ type: 'prob', isValueType: true, description: 'Probability value' },
// possible data types
{ key: 'z', label: 'type', description: 'Z-statistic' },
{ key: 't', label: 'type', description: 'T-statistic' },
{ key: 'p', label: 'type', description: 'p-value' },
{ key: 'logp', label: 'type', description: 'Negative base-ten logarithm of p-value' },
{ key: 'chi2', label: 'type', description: 'Chi-squared value' },
{ key: 'prob', label: 'type', description: 'Probability value' },
{
type: 'stat',
isValueType: true,
key: 'stat',
label: 'type',
description: 'Test value of meta-analytic algorithm (e.g., ALE values for ALE, OF values for MKDA)',
},
{ type: 'est', isValueType: true, description: 'Parameter estimate (IBMA only)' },
{ type: 'se', isValueType: true, description: 'Standard error of the parameter estimate (IBMA only)' },
{ type: 'tau2', isValueType: true, description: 'Estimated between-study variance (IBMA only)' },
{ type: 'sigma2', isValueType: true, description: 'Estimated within-study variance (IBMA only)' },
{ type: 'label', isValueType: true, description: 'Label map' },
{ key: 'est', label: 'type', description: 'Parameter estimate (IBMA only)' },
{ key: 'se', label: 'type', description: 'Standard error of the parameter estimate (IBMA only)' },
{ key: 'tau2', label: 'type', description: 'Estimated between-study variance (IBMA only)' },
{ key: 'sigma2', label: 'type', description: 'Estimated within-study variance (IBMA only)' },
{ key: 'label', label: 'type', description: 'Label map' },
// KVPs that describe the methods applied to generate the meta analysis
{
type: 'desc',
isValueType: false,
key: 'desc',
label: 'description',
description:
'Description of the data type. Only used when multiple maps with the same data type are produced by the same method.',
},
{
type: 'level',
isValueType: false,
key: 'level',
label: 'level',
description: 'Level of multiple comparisons correction. Either cluster or voxel.',
},
{
type: 'corr',
isValueType: false,
key: 'corr',
label: 'correction',
description:
'Type of multiple comparisons correction. Either FWE (familywise error rate) or FDR (false discovery rate).',
},
{
type: 'method',
isValueType: false,
key: 'method',
label: 'method',
description:
'Name of the method used for multiple comparisons correction (e.g., “montecarlo” for a Monte Carlo procedure).',
},
{
type: 'diag',
isValueType: false,
key: 'diag',
label: 'diagnostic',
description:
'Type of diagnostic. Either Jackknife (jackknife analysis) or FocusCounter (focus-count analysis).',
},
{
type: 'tab',
isValueType: false,
key: 'tab',
label: 'table',
description: 'Type of table. Either clust (clusters table) or counts (contribution table).',
},
{ type: 'tail', isValueType: false, description: 'Sign of the tail for label maps. Either positive or negative.' },
{ key: 'tail', label: 'tail', description: 'Sign of the tail for label maps. Either positive or negative.' },
];

export const parseNimareFileName = (fileName: string | undefined | null) => {
export const parseNimareFileName = (
fileName: string | undefined | null
): { key: string; label: string; description: string; value: string }[] => {
// we expect filenames of the form: z_desc-somedescription_level-voxel_corr-fwe_method-montecarlo.nii.gz
if (!fileName) return [];
const segments = fileName.replace('.nii.gz', '').split('_');
return segments.map((segment) => {
const [key, value] = segment.split('-');
const nimareOutput = NimareOutputs.find((output) => output.type === key);
if (value === undefined) {
// value type, not a meta analysis method or descriptor
const associatedNimareOutput = NimareOutputs.find((output) => output.key === key);

if (associatedNimareOutput === undefined) {
// unrecognized KVP in file name
return {
key: 'type',
isValueType: nimareOutput?.isValueType || false,
keyDesc: 'The type of data in the map.',
value: nimareOutput?.type || '',
key: key,
label: 'unknown field',
description: '',
value: key,
};
} else if (value === undefined) {
// data type, not a method. The key is also the value
return {
...associatedNimareOutput,
value: key,
};
} else {
// KVPs describing methods applied to generate the map
return {
key: key,
isValueType: nimareOutput?.isValueType || false,
keyDesc: nimareOutput?.description || '',
...associatedNimareOutput,
value: value,
};
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,36 +1,145 @@
import { render } from '@testing-library/react';
import { render, screen } from '@testing-library/react';
import DisplayMetaAnalysisResults from './DisplayMetaAnalysisResults';
import { useGetMetaAnalysisResultById } from 'hooks';
import { Mock } from 'vitest';
import { INeurovault } from 'hooks/metaAnalyses/useGetNeurovaultImages';
import { useGetNeurovaultImages } from 'hooks';
import { mockMetaAnalysisReturn } from 'testing/mockData';
import { Specification } from 'neurosynth-compose-typescript-sdk';

vi.mock('hooks');
vi.mock('pages/MetaAnalysis/components/MetaAnalysisResultStatusAlert');
vi.mock('pages/MetaAnalysis/components/DisplayParsedNiMareFile');
vi.mock('components/Visualizer/NiiVueVisualizer');

const caseA: INeurovault[] = [];
const caseB: INeurovault[] = [];
const caseC: INeurovault[] = [];
const caseD: INeurovault[] = [];
const caseNonsenseValues: Partial<INeurovault>[] = [
{ id: 2, name: 'BNonsensicalValue' },
{ id: 3, name: 'ZAnotherNonsenseValue' },
{ id: 1, name: 'ArandomValue' },
];
const caseRandomOrderDataTypes: Partial<INeurovault>[] = [
{ id: 12, name: 'label_desc-ABC' },
{ id: 3, name: 'p_desc-ABC' },
{ id: 2, name: 't_desc-ABC' },
{ id: 11, name: 'sigma2_desc-ABC' },
{ id: 5, name: 'chi2_desc-ABC' },
{ id: 4, name: 'logp_desc-ABC' },
{ id: 8, name: 'est_desc-ABC' },
{ id: 10, name: 'tau2_desc-ABC' },
{ id: 1, name: 'z_desc-ABC' },
{ id: 6, name: 'prob_desc-ABC' },
{ id: 9, name: 'se_desc-ABC' },
{ id: 7, name: 'stat_desc-ABC' },
];
const caseSameKeyDifferentValues: Partial<INeurovault>[] = [
{ id: 2, name: 'z_desc-DEF' },
{ id: 3, name: 'z_desc-ZZZ' },
{ id: 1, name: 'z_desc-ABC' },
];
const caseVoxelAndCluster: Partial<INeurovault>[] = [
{ id: 2, name: 'z_desc-ABC_level-voxel' },
{ id: 1, name: 'z_desc-ABC_level-cluster' },
];

const caseMKDAChi2: Partial<INeurovault>[] = [
{ id: 1, name: 'z_desc-AAA' },
{ id: 2, name: 'z_desc-CCC' },
{ id: 3, name: 'z_desc-ZZZ' },
{ id: 4, name: 'z_desc-uniformityMass' },
{ id: 5, name: 'z_desc-associationMass' },
];

describe('DisplayMetaAnalysisResults', () => {
it('should render', () => {
render(<DisplayMetaAnalysisResults metaAnalysis={undefined} />);
});

it('should show the correctly sorted list (1)', () => {
(useGetMetaAnalysisResultById as Mock).mockReturnValue({
data: caseA,
it('should show the correctly sorted list for nonsense values, sorting by alphabetical order', () => {
(useGetNeurovaultImages as Mock).mockReturnValue({
data: caseNonsenseValues,
isLoading: false,
isError: false,
});

// Passing in a met-analysis is not important as we mock the hook that provides the important data
render(<DisplayMetaAnalysisResults metaAnalysis={undefined} />);
render(<DisplayMetaAnalysisResults metaAnalysis={mockMetaAnalysisReturn()} />);
const buttons = screen.getAllByRole('button');
expect(buttons.length).toEqual(caseNonsenseValues.length);
expect(buttons[0].textContent).toBe('ArandomValue');
expect(buttons[1].textContent).toBe('BNonsensicalValue');
expect(buttons[2].textContent).toBe('ZAnotherNonsenseValue');
});

it('should show the correctly sorted list for data types', () => {
(useGetNeurovaultImages as Mock).mockReturnValue({
data: caseRandomOrderDataTypes,
isLoading: false,
isError: false,
});

// Passing in a meta-analysis as an argument is not necessary as we mock the hook that provides the actual data
render(<DisplayMetaAnalysisResults metaAnalysis={mockMetaAnalysisReturn()} />);
const buttons = screen.getAllByRole('button');
expect(buttons.length).toEqual(caseRandomOrderDataTypes.length);
expect(buttons[0].textContent).toBe('z_desc-ABC');
expect(buttons[1].textContent).toBe('t_desc-ABC');
expect(buttons[2].textContent).toBe('p_desc-ABC');
expect(buttons[3].textContent).toBe('logp_desc-ABC');
expect(buttons[4].textContent).toBe('chi2_desc-ABC');
expect(buttons[5].textContent).toBe('prob_desc-ABC');
expect(buttons[6].textContent).toBe('stat_desc-ABC');
expect(buttons[7].textContent).toBe('est_desc-ABC');
expect(buttons[8].textContent).toBe('se_desc-ABC');
expect(buttons[9].textContent).toBe('tau2_desc-ABC');
expect(buttons[10].textContent).toBe('sigma2_desc-ABC');
expect(buttons[11].textContent).toBe('label_desc-ABC');
});

it('should show the correctly sorted list for same key different values', () => {
(useGetNeurovaultImages as Mock).mockReturnValue({
data: caseSameKeyDifferentValues,
isLoading: false,
isError: false,
});

expect();
// Passing in a meta-analysis as an argument is not necessary as we mock the hook that provides the actual data
render(<DisplayMetaAnalysisResults metaAnalysis={mockMetaAnalysisReturn()} />);
const buttons = screen.getAllByRole('button');
expect(buttons.length).toEqual(caseSameKeyDifferentValues.length);
expect(buttons[0].textContent).toBe('z_desc-ABC');
expect(buttons[1].textContent).toBe('z_desc-DEF');
expect(buttons[2].textContent).toBe('z_desc-ZZZ');
});

it('should show the correctly sorted list (2)', () => {});
it('should show the correctly sorted list and prioritize cluster and then voxel', () => {
(useGetNeurovaultImages as Mock).mockReturnValue({
data: caseVoxelAndCluster,
isLoading: false,
isError: false,
});

render(<DisplayMetaAnalysisResults metaAnalysis={mockMetaAnalysisReturn()} />);
const buttons = screen.getAllByRole('button');
expect(buttons.length).toEqual(caseVoxelAndCluster.length);
expect(buttons[0].textContent).toBe('z_desc-ABC_level-cluster');
expect(buttons[1].textContent).toBe('z_desc-ABC_level-voxel');
});

it('should show the correctly sorted list for MKDAChi2', () => {
const mockMetaAnalysis = mockMetaAnalysisReturn();
(mockMetaAnalysis.specification as Specification).estimator = { type: 'MKDAChi2' };

(useGetNeurovaultImages as Mock).mockReturnValue({
data: caseMKDAChi2,
isLoading: false,
isError: false,
});

render(<DisplayMetaAnalysisResults metaAnalysis={mockMetaAnalysis} />);
const buttons = screen.getAllByRole('button');
expect(buttons.length).toEqual(caseMKDAChi2.length);
expect(buttons[0].textContent).toBe('z_desc-associationMass');
expect(buttons[1].textContent).toBe('z_desc-uniformityMass');
expect(buttons[2].textContent).toBe('z_desc-AAA');
expect(buttons[3].textContent).toBe('z_desc-CCC');
expect(buttons[4].textContent).toBe('z_desc-ZZZ');
});
});
Loading

0 comments on commit 7a84982

Please sign in to comment.