diff --git a/packages/components/src/internal/components/ontology/ConceptInformationTabs.spec.tsx b/packages/components/src/internal/components/ontology/ConceptInformationTabs.spec.tsx deleted file mode 100644 index 7a3891b37b..0000000000 --- a/packages/components/src/internal/components/ontology/ConceptInformationTabs.spec.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import React from 'react'; -import { mount, ReactWrapper } from 'enzyme'; - -import { ConceptInformationTabs, ConceptInfoTabs } from './ConceptInformationTabs'; -import { ConceptOverviewPanelImpl } from './ConceptOverviewPanel'; -import { ConceptModel } from './models'; -import { ConceptPathInfo } from './ConceptPathInfo'; - -const DEFAULT_PROPS = { - concept: undefined, -}; - -describe('ConceptInformationTabs', () => { - function validate(wrapper: ReactWrapper) { - expect(wrapper.find('li[role="presentation"]')).toHaveLength(2); - expect(wrapper.find('.ontology-concept-overview-container')).toHaveLength(2); - expect(wrapper.find(ConceptOverviewPanelImpl)).toHaveLength(1); - expect(wrapper.find(ConceptPathInfo)).toHaveLength(1); - } - - test('no concept', () => { - const wrapper = mount(); - validate(wrapper); - expect(wrapper.find(ConceptOverviewPanelImpl).prop('concept')).toBe(undefined); - wrapper.unmount(); - }); - - test('with concept', () => { - const concept = new ConceptModel({ code: 'a', label: 'b' }); - const wrapper = mount( - - ); - validate(wrapper); - expect(wrapper.find(ConceptOverviewPanelImpl).prop('concept')).toBe(concept); - wrapper.unmount(); - }); -}); diff --git a/packages/components/src/internal/components/ontology/ConceptInformationTabs.test.tsx b/packages/components/src/internal/components/ontology/ConceptInformationTabs.test.tsx new file mode 100644 index 0000000000..62233d9f7d --- /dev/null +++ b/packages/components/src/internal/components/ontology/ConceptInformationTabs.test.tsx @@ -0,0 +1,38 @@ +import React from 'react'; +import { render } from '@testing-library/react'; + +import { ConceptInformationTabs } from './ConceptInformationTabs'; +import { ConceptModel } from './models'; +import { waitFor } from '@testing-library/dom'; + +jest.mock('./actions', () => ({ + ...jest.requireActual('./actions'), + fetchAlternatePaths: jest.fn().mockResolvedValue([]), +})); + +const DEFAULT_PROPS = { + concept: undefined, + alternatePathClickHandler: jest.fn(), +}; + +describe('ConceptInformationTabs', () => { + test('no concept', () => { + const { container } = render(); + expect(container.querySelectorAll('li[role="presentation"]')).toHaveLength(2); + expect(container.querySelectorAll('.ontology-concept-overview-container')).toHaveLength(1); + expect(container.querySelectorAll('.ontology-concept-pathinfo-container')).toHaveLength(1); + expect(container.querySelectorAll('.none-selected')).toHaveLength(2); + }); + + test('with concept', async () => { + const concept = new ConceptModel({ code: 'a', label: 'b' }); + const { container } = render(); + await waitFor(() => { + expect(container.querySelectorAll('li[role="presentation"]')).toHaveLength(2); + }); + expect(container.querySelectorAll('.ontology-concept-overview-container')).toHaveLength(1); + expect(container.querySelectorAll('.ontology-concept-pathinfo-container')).toHaveLength(1); + expect(container.querySelector('.title').textContent).toBe('b'); + expect(container.querySelector('.code').textContent).toBe('a'); + }); +}); diff --git a/packages/components/src/internal/components/ontology/ConceptOverviewPanel.spec.tsx b/packages/components/src/internal/components/ontology/ConceptOverviewPanel.spec.tsx deleted file mode 100644 index 7b7cc911bd..0000000000 --- a/packages/components/src/internal/components/ontology/ConceptOverviewPanel.spec.tsx +++ /dev/null @@ -1,152 +0,0 @@ -import React from 'react'; -import { mount, ReactWrapper } from 'enzyme'; - -import { Alert } from '../base/Alert'; - -import { LabelHelpTip } from '../base/LabelHelpTip'; - -import { - ConceptOverviewTooltip, - ConceptOverviewPanelImpl, - ConceptSynonyms, - OntologyConceptOverviewPanel, -} from './ConceptOverviewPanel'; -import { ConceptPathDisplay } from './ConceptPathDisplay'; -import { ConceptModel, PathModel } from './models'; -import { waitForLifecycle } from '../../test/enzymeTestHelpers'; - -const TEST_CONCEPT = new ConceptModel({ code: 'a', label: 'b', description: 'c' }); -const TEST_PATH = new PathModel({}); - -describe('OntologyConceptOverviewPanel', () => { - test('without code prop', () => { - const wrapper = mount(); - expect(wrapper.find(Alert)).toHaveLength(1); - expect(wrapper.find(Alert).text()).toBe(''); - expect(wrapper.find(ConceptOverviewPanelImpl)).toHaveLength(0); - wrapper.unmount(); - }); -}); - -describe('ConceptSynonyms', () => { - test('without synonyms', () => { - let wrapper = mount(); - expect(wrapper.find('.synonyms-title')).toHaveLength(0); - expect(wrapper.find('.synonyms-text')).toHaveLength(0); - wrapper.unmount(); - - wrapper = mount(); - expect(wrapper.find('.synonyms-title')).toHaveLength(0); - expect(wrapper.find('.synonyms-text')).toHaveLength(0); - wrapper.unmount(); - }); - - test('with sorted synonyms', () => { - const wrapper = mount(); - expect(wrapper.find('.synonyms-title')).toHaveLength(1); - expect(wrapper.find('.synonyms-text')).toHaveLength(1); - expect(wrapper.find('li')).toHaveLength(3); - expect(wrapper.find('li').at(0).text()).toBe('a'); - expect(wrapper.find('li').at(1).text()).toBe('b'); - expect(wrapper.find('li').at(2).text()).toBe('c'); - wrapper.unmount(); - }); -}); - -describe('ConceptOverviewPanelImpl', () => { - function validate( - wrapper: ReactWrapper, - concept: ConceptModel, - divCount = 1, - hasSelectedPath = false, - showPath = false - ): void { - expect(wrapper.find('.none-selected')).toHaveLength(concept === undefined ? 1 : 0); - expect(wrapper.find('div')).toHaveLength(divCount); - expect(wrapper.find(ConceptPathDisplay)).toHaveLength(showPath ? 1 : 0); - - expect(wrapper.find('button')).toHaveLength(hasSelectedPath ? 1 : 0); - if (hasSelectedPath) { - expect(wrapper.find('button').text()).toBe(showPath ? 'Hide Path' : 'Show Path'); - } - - if (concept) { - expect(wrapper.find('.title').first().text()).toBe(concept.label); - expect(wrapper.find('.code').first().text()).toBe(concept.code); - expect(wrapper.find('.description-title').text()).toBe('Description'); - expect(wrapper.find('.description-text').text()).toBe(concept.description); - } - } - - test('no concept', () => { - const wrapper = mount(); - validate(wrapper, undefined); - wrapper.unmount(); - }); - - test('with concept', () => { - const wrapper = mount(); - validate(wrapper, TEST_CONCEPT, 3); - wrapper.unmount(); - }); - - test('with selected path, not shown', () => { - const wrapper = mount(); - validate(wrapper, TEST_CONCEPT, 4, true); - wrapper.unmount(); - }); - - test('with selected path, shown', () => { - const wrapper = mount(); - wrapper.find('button').simulate('click'); - validate(wrapper, TEST_CONCEPT, 8, true, true); - expect(wrapper.find(ConceptPathDisplay).prop('title')).toBe('Current Path'); - expect(wrapper.find(ConceptPathDisplay).prop('path')).toBe(TEST_PATH); - expect(wrapper.find(ConceptPathDisplay).prop('isSelected')).toBe(true); - wrapper.unmount(); - }); -}); - -describe('ConceptOverviewToolTip', () => { - async function validate(wrapper: ReactWrapper, concept?: ConceptModel, errorTxt?: string): void { - expect(wrapper.find(LabelHelpTip)).toHaveLength(errorTxt ? 0 : 1); - expect(wrapper.find(Alert)).toHaveLength(1); - expect(wrapper.find(Alert).text()).toBe(errorTxt ?? ''); - const infoIcon = wrapper.find('.fa-info-circle'); - expect(infoIcon).toHaveLength(errorTxt ? 0 : !concept ? 0 : 1); - - if (infoIcon.length > 0) { - wrapper.find('.overlay-trigger').simulate('mouseenter'); - await waitForLifecycle(wrapper); - const over = wrapper.find('.ontology-concept-overview-container'); - expect(over).toHaveLength(errorTxt ? 0 : 1); - expect(over.find(ConceptOverviewPanelImpl)).toHaveLength(errorTxt ? 0 : 1); - } - } - - test('no concept', async () => { - const wrapper = mount(); - await validate(wrapper); - wrapper.unmount(); - }); - - test('with concept', async () => { - const wrapper = mount(); - await validate(wrapper, TEST_CONCEPT); - expect(wrapper.find(ConceptOverviewPanelImpl).prop('concept')).toBe(TEST_CONCEPT); - wrapper.unmount(); - }); - - test('with path', async () => { - const wrapper = mount(); - await validate(wrapper, TEST_CONCEPT); - expect(wrapper.find(ConceptOverviewPanelImpl).prop('selectedPath')).toBe(TEST_PATH); - wrapper.unmount(); - }); - - test('error', async () => { - const wrapper = mount(); - await validate(wrapper, TEST_CONCEPT, 'test error'); - wrapper.unmount(); - }); -}); diff --git a/packages/components/src/internal/components/ontology/ConceptOverviewPanel.test.tsx b/packages/components/src/internal/components/ontology/ConceptOverviewPanel.test.tsx new file mode 100644 index 0000000000..d5268fd73f --- /dev/null +++ b/packages/components/src/internal/components/ontology/ConceptOverviewPanel.test.tsx @@ -0,0 +1,119 @@ +import React from 'react'; +import { fireEvent, render } from '@testing-library/react'; + +import { + ConceptOverviewPanelImpl, + ConceptOverviewTooltip, + ConceptSynonyms, + OntologyConceptOverviewPanel, +} from './ConceptOverviewPanel'; +import { ConceptModel, PathModel } from './models'; +import { waitFor } from '@testing-library/dom'; + +jest.mock('./actions', () => ({ + ...jest.requireActual('./actions'), + fetchConceptForCode: jest.fn().mockResolvedValue(undefined), + fetchParentPaths: jest.fn().mockResolvedValue([]), +})); + +const TEST_CONCEPT = new ConceptModel({ code: 'a', label: 'b', description: 'c' }); +const TEST_PATH = new PathModel({}); + +describe('OntologyConceptOverviewPanel', () => { + test('without code prop', () => { + const { container } = render(); + expect(container.querySelector('[role="alert"]')).toBeNull(); + expect(container.querySelector('.none-selected')).toBeNull(); + expect(container.querySelector('.title')).toBeNull(); + }); +}); + +describe('ConceptSynonyms', () => { + test('without synonyms', () => { + const { container, rerender } = render(); + expect(container.querySelector('.synonyms-title')).toBeNull(); + expect(container.querySelector('.synonyms-text')).toBeNull(); + + rerender(); + expect(container.querySelector('.synonyms-title')).toBeNull(); + expect(container.querySelector('.synonyms-text')).toBeNull(); + }); + + test('with sorted synonyms', () => { + const { container } = render(); + expect(container.querySelector('.synonyms-title')).not.toBeNull(); + expect(container.querySelector('.synonyms-text')).not.toBeNull(); + const items = container.querySelectorAll('li'); + expect(items).toHaveLength(3); + expect(items[0].textContent).toBe('a'); + expect(items[1].textContent).toBe('b'); + expect(items[2].textContent).toBe('c'); + }); +}); + +describe('ConceptOverviewPanelImpl', () => { + test('no concept', () => { + const { container } = render(); + expect(container.querySelector('.none-selected').textContent).toBe('No concept selected'); + expect(container.querySelector('.title')).toBeNull(); + expect(container.querySelector('.code')).toBeNull(); + }); + + test('with concept', () => { + const { container } = render(); + expect(container.querySelector('.none-selected')).toBeNull(); + expect(container.querySelector('button')).toBeNull(); + expect(container.querySelector('.title').textContent).toBe('b'); + expect(container.querySelector('.code').textContent).toBe('a'); + expect(container.querySelector('.description-title').textContent).toBe('Description'); + expect(container.querySelector('.description-text').textContent).toBe('c'); + }); + + test('with selected path, not shown', () => { + const { container } = render(); + const button = container.querySelector('button'); + expect(button.textContent).toBe('Show Path'); + expect(container.querySelector('.concept-overview-selected-path')).toBeNull(); + }); + + test('with selected path, shown', async () => { + const { container } = render(); + fireEvent.click(container.querySelector('button')); + await waitFor(() => { + expect(container.querySelector('button').textContent).toBe('Hide Path'); + }); + expect(container.querySelector('.concept-overview-selected-path')).not.toBeNull(); + expect(container.querySelector('.concept-path-container.selected')).not.toBeNull(); + }); +}); + +describe('ConceptOverviewTooltip', () => { + test('no concept', () => { + const { container } = render(); + expect(container.querySelector('[role="alert"]')).toBeNull(); + expect(container.querySelector('.fa-info-circle')).toBeNull(); + expect(container.querySelector('.overlay-trigger')).not.toBeNull(); + }); + + test('with concept', async () => { + const { container } = render(); + expect(container.querySelector('[role="alert"]')).toBeNull(); + expect(container.querySelector('.fa-info-circle')).not.toBeNull(); + fireEvent.mouseEnter(container.querySelector('.overlay-trigger')); + await waitFor(() => { + expect(document.querySelector('.ontology-concept-overview-container')).not.toBeNull(); + }); + }); + + test('with path', () => { + const { container } = render(); + expect(container.querySelector('[role="alert"]')).toBeNull(); + expect(container.querySelector('.fa-info-circle')).not.toBeNull(); + }); + + test('error', () => { + const { container } = render(); + expect(container.querySelector('[role="alert"]').textContent).toBe('test error'); + expect(container.querySelector('.overlay-trigger')).toBeNull(); + }); +}); diff --git a/packages/components/src/internal/components/ontology/ConceptPathDisplay.spec.tsx b/packages/components/src/internal/components/ontology/ConceptPathDisplay.spec.tsx deleted file mode 100644 index 8db79bcd99..0000000000 --- a/packages/components/src/internal/components/ontology/ConceptPathDisplay.spec.tsx +++ /dev/null @@ -1,134 +0,0 @@ -import React from 'react'; -import { mount, ReactWrapper } from 'enzyme'; - -import { Alert } from '../base/Alert'; - -import { ConceptPathDisplay, ConceptPathDisplayImpl } from './ConceptPathDisplay'; - -import { PathModel } from './models'; - -const TEST_CONCEPT_PATH = new PathModel(); - -describe('ConceptPathDisplay', () => { - test('Path not set', () => { - const wrapper = mount(); - expect(wrapper.find(Alert)).toHaveLength(1); - expect(wrapper.find(Alert).text()).toBe(''); - expect(wrapper.find(ConceptPathDisplayImpl)).toHaveLength(0); - wrapper.unmount(); - }); - - test('Path set', () => { - const wrapper = mount(); - expect(wrapper.find(ConceptPathDisplayImpl)).toHaveLength(1); - expect(wrapper.find(ConceptPathDisplayImpl).prop('path')).toBe(TEST_CONCEPT_PATH); - expect(wrapper.find(ConceptPathDisplayImpl).prop('title')).toBe('test title'); - expect(wrapper.find(ConceptPathDisplayImpl).prop('isSelected')).toBe(true); - expect(wrapper.find(ConceptPathDisplayImpl).prop('parentPaths')).toBe(undefined); - wrapper.unmount(); - }); -}); - -describe('ConceptPathDisplayImpl', () => { - function validate( - wrapper: ReactWrapper, - path: PathModel = undefined, - parentCount = 0, - title: string = undefined, - isSelected = false, - isLoading = false - ): void { - expect(wrapper.find('.concept-path-container')).toHaveLength(path ? 1 : 0); - expect(wrapper.find('.concept-path')).toHaveLength(path ? 1 : 0); - expect(wrapper.find('.selected')).toHaveLength(isSelected ? 1 : 0); - expect(wrapper.find('.concept-path-label')).toHaveLength(parentCount); - expect(wrapper.find('i')).toHaveLength(parentCount === 0 ? (isLoading ? 1 : 0) : parentCount - 1); - expect(wrapper.find('.concept-path-spacer')).toHaveLength(parentCount > 0 ? parentCount - 1 : 0); - expect(wrapper.find('.title')).toHaveLength(title ? 1 : 0); - - if (title) { - expect(wrapper.find('.title').text()).toBe(title); - } - } - - test('No path loaded', () => { - const wrapper = mount(); - validate(wrapper); - wrapper.unmount(); - }); - - test('Parent path not loaded yet', () => { - const wrapper = mount(); - validate(wrapper, TEST_CONCEPT_PATH, 0, undefined, false, true); - wrapper.unmount(); - }); - - test('Parent path empty', () => { - const parentPaths = []; - const wrapper = mount(); - validate(wrapper, TEST_CONCEPT_PATH, parentPaths.length); - wrapper.unmount(); - }); - - test('Parent path set', () => { - const pathModel1 = new PathModel({ - label: 'first', - }); - - const parentPaths = [pathModel1]; - const wrapper = mount(); - validate(wrapper, TEST_CONCEPT_PATH, parentPaths.length); - - expect(wrapper.find('.concept-path-label').at(0).text()).toBe('first'); - wrapper.unmount(); - }); - - test('Parent path set', () => { - const pathModel1 = new PathModel({ - label: 'first', - }); - - const pathModel2 = new PathModel({ - label: 'second', - }); - - const pathModel3 = new PathModel({ - label: 'third', - }); - - const parentPaths = [pathModel1, pathModel2, pathModel3]; - const wrapper = mount(); - validate(wrapper, TEST_CONCEPT_PATH, parentPaths.length); - - expect(wrapper.find('.concept-path-label').at(0).text()).toBe('first'); - expect(wrapper.find('.concept-path-label').at(1).text()).toBe('second'); - expect(wrapper.find('.concept-path-label').at(2).text()).toBe('third'); - wrapper.unmount(); - }); - - test('Title set', () => { - const parentPaths = []; - const title = 'Long title to show'; - const wrapper = mount( - - ); - validate(wrapper, TEST_CONCEPT_PATH, parentPaths.length, title); - wrapper.unmount(); - }); - - test('Selected set', () => { - const parentPaths = []; - const title = 'Long title to show'; - const selected = true; - const wrapper = mount( - - ); - validate(wrapper, TEST_CONCEPT_PATH, parentPaths.length, title, selected); - wrapper.unmount(); - }); -}); diff --git a/packages/components/src/internal/components/ontology/ConceptPathDisplay.test.tsx b/packages/components/src/internal/components/ontology/ConceptPathDisplay.test.tsx new file mode 100644 index 0000000000..ad4b7f73fe --- /dev/null +++ b/packages/components/src/internal/components/ontology/ConceptPathDisplay.test.tsx @@ -0,0 +1,98 @@ +import React from 'react'; +import { render } from '@testing-library/react'; + +import { ConceptPathDisplay, ConceptPathDisplayImpl } from './ConceptPathDisplay'; +import { PathModel } from './models'; +import { waitFor } from '@testing-library/dom'; + +jest.mock('./actions', () => ({ + ...jest.requireActual('./actions'), + fetchParentPaths: jest.fn().mockResolvedValue([]), +})); + +const TEST_CONCEPT_PATH = new PathModel(); + +describe('ConceptPathDisplay', () => { + test('Path not set', () => { + const { container } = render(); + expect(container.querySelector('[role="alert"]')).toBeNull(); + expect(container.querySelector('.concept-path-container')).toBeNull(); + }); + + test('Path set', async () => { + const { container } = render( + + ); + await waitFor(() => { + expect(container.querySelector('.concept-path-container.selected')).not.toBeNull(); + }); + expect(container.querySelector('.title').textContent).toBe('test title'); + expect(container.querySelector('.fa-spinner')).toBeNull(); + }); +}); + +describe('ConceptPathDisplayImpl', () => { + test('No path loaded', () => { + const { container } = render(); + expect(container.querySelector('.concept-path-container')).toBeNull(); + expect(container.querySelector('.concept-path')).toBeNull(); + }); + + test('Parent path not loaded yet', () => { + const { container } = render(); + expect(container.querySelector('.concept-path-container')).not.toBeNull(); + expect(container.querySelector('.concept-path')).not.toBeNull(); + expect(container.querySelector('.selected')).toBeNull(); + expect(container.querySelector('.fa-spinner')).not.toBeNull(); + expect(container.querySelectorAll('.concept-path-label')).toHaveLength(0); + }); + + test('Parent path empty', () => { + const { container } = render(); + expect(container.querySelector('.concept-path-container')).not.toBeNull(); + expect(container.querySelector('.fa-spinner')).toBeNull(); + expect(container.querySelectorAll('.concept-path-label')).toHaveLength(0); + expect(container.querySelectorAll('.concept-path-spacer')).toHaveLength(0); + }); + + test('Parent path set with one path', () => { + const parentPaths = [new PathModel({ label: 'first' })]; + const { container } = render(); + const labels = container.querySelectorAll('.concept-path-label'); + expect(labels).toHaveLength(1); + expect(labels[0].textContent).toBe('first'); + expect(container.querySelectorAll('.concept-path-spacer')).toHaveLength(0); + }); + + test('Parent path set with multiple paths', () => { + const parentPaths = [ + new PathModel({ label: 'first' }), + new PathModel({ label: 'second' }), + new PathModel({ label: 'third' }), + ]; + const { container } = render(); + const labels = container.querySelectorAll('.concept-path-label'); + expect(labels).toHaveLength(3); + expect(labels[0].textContent).toBe('first'); + expect(labels[1].textContent).toBe('second'); + expect(labels[2].textContent).toBe('third'); + expect(container.querySelectorAll('.concept-path-spacer')).toHaveLength(2); + }); + + test('Title set', () => { + const title = 'Long title to show'; + const { container } = render( + + ); + expect(container.querySelector('.title').textContent).toBe(title); + }); + + test('Selected set', () => { + const title = 'Long title to show'; + const { container } = render( + + ); + expect(container.querySelector('.concept-path-container.selected')).not.toBeNull(); + expect(container.querySelector('.title').textContent).toBe(title); + }); +}); diff --git a/packages/components/src/internal/components/ontology/ConceptPathDisplay.tsx b/packages/components/src/internal/components/ontology/ConceptPathDisplay.tsx index 3dea97ae1e..b3c07c0d0f 100644 --- a/packages/components/src/internal/components/ontology/ConceptPathDisplay.tsx +++ b/packages/components/src/internal/components/ontology/ConceptPathDisplay.tsx @@ -70,12 +70,12 @@ export const ConceptPathDisplayImpl: FC = memo(prop {!parentPaths && } {parentPaths?.map((parent, idx) => { return ( - <> + {parent.label} {idx !== parentPaths.length - 1 && ( )} - + ); })} diff --git a/packages/components/src/internal/components/ontology/ConceptPathInfo.spec.tsx b/packages/components/src/internal/components/ontology/ConceptPathInfo.spec.tsx deleted file mode 100644 index 2ab58b9691..0000000000 --- a/packages/components/src/internal/components/ontology/ConceptPathInfo.spec.tsx +++ /dev/null @@ -1,231 +0,0 @@ -import React from 'react'; -import { mount, ReactWrapper } from 'enzyme'; - -import { Alert } from '../base/Alert'; - -import { LoadingSpinner } from '../base/LoadingSpinner'; - -import { PathModel } from './models'; -import { AlternatePathPanel, ConceptPathInfo, ConceptPathInfoImpl } from './ConceptPathInfo'; - -import { ConceptPathDisplay } from './ConceptPathDisplay'; - -describe('ConceptPathInfo', () => { - test('Nothing set', () => { - const wrapper = mount(); - expect(wrapper.find(Alert)).toHaveLength(1); - expect(wrapper.find(Alert).text()).toBe(''); - expect(wrapper.find(ConceptPathInfoImpl)).toHaveLength(1); - expect(wrapper.find(ConceptPathInfoImpl).prop('selectedCode')).toBe(undefined); - expect(wrapper.find(ConceptPathInfoImpl).prop('alternatePaths')).toBe(undefined); - wrapper.unmount(); - }); -}); - -describe('ConceptPathInfoImpl', () => { - function validate( - wrapper: ReactWrapper, - code: string = undefined, - loadingCount = 0, - alternatePaths: PathModel[] = undefined, - selectedPath: PathModel = undefined - ): void { - expect(wrapper.find('.none-selected')).toHaveLength(code ? 0 : 1); - expect(wrapper.find(LoadingSpinner)).toHaveLength(loadingCount); - expect(wrapper.find(ConceptPathDisplay)).toHaveLength(alternatePaths?.length ? alternatePaths.length : 0); - expect(wrapper.find('.current-path-container')).toHaveLength( - alternatePaths?.length > 0 && selectedPath ? 1 : 0 - ); - expect(wrapper.find('.current-path-container')?.find(ConceptPathDisplay)).toHaveLength( - alternatePaths?.length > 0 && selectedPath ? 1 : 0 - ); - expect(wrapper.find('.current-path-container')?.find('.selected')).toHaveLength( - alternatePaths?.length > 0 && selectedPath ? 1 : 0 - ); - expect(wrapper.find('.alternate-paths-container')).toHaveLength(alternatePaths?.length > 0 ? 1 : 0); - expect(wrapper.find('.alternate-paths-container')?.find(ConceptPathDisplay)).toHaveLength( - alternatePaths?.length > 0 ? alternatePaths.length - 1 : 0 - ); - expect(wrapper.find('.no-path-info')).toHaveLength(alternatePaths?.length <= 1 ? 1 : 0); - } - - test('Nothing set', () => { - const wrapper = mount(); - expect(wrapper.find('.none-selected')).toHaveLength(1); - expect(wrapper.find('.none-selected').text()).toBe('No concept selected'); - expect(wrapper.find('.concept-pathinfo-container')).toHaveLength(0); - expect(wrapper.find(ConceptPathDisplay)).toHaveLength(0); - wrapper.unmount(); - }); - - test('Code set, aka Loading', () => { - const code = 'MagicCode'; - const wrapper = mount(); - validate(wrapper, code, 1); - wrapper.unmount(); - }); - - test('Loading, Selected path set, alternate paths undefined', () => { - const code = 'MagicCode'; - const path = new PathModel({ - path: 'abcd/efg/', - label: 'first', - }); - const alternatePaths = undefined; - const wrapper = mount( - - ); - validate(wrapper, code, 1, alternatePaths, path); - wrapper.unmount(); - }); - - /** - * NOTE: this scenario should be impossible in reality as selectedPath should always be included in the alternatePaths set - */ - test('Selected path set, alternate paths empty', () => { - const code = 'MagicCode'; - const path = new PathModel({ - path: 'abcd/efg/', - label: 'first', - }); - const alternatePaths = []; - const wrapper = mount( - - ); - validate(wrapper, code, 0, alternatePaths, path); - wrapper.unmount(); - }); - - test('Only selected path', () => { - const code = 'MagicCode'; - const path = new PathModel({ - path: 'abcd/efg/', - label: 'first', - }); - const alternatePaths = [path]; - const wrapper = mount( - - ); - validate(wrapper, code, 1, alternatePaths, path); - wrapper.unmount(); - }); - - test('Selected path, and alternate paths', () => { - const code = 'MagicCode'; - const path1 = new PathModel({ - path: 'abcd/efg/', - label: 'first', - }); - - const path2 = new PathModel({ - path: '1234/efg/', - label: 'second', - }); - const path3 = new PathModel({ - path: 'abcd/efg/123', - label: 'third', - }); - const path4 = new PathModel({ - path: 'abcd/efg/4', - label: 'fourth', - }); - const selected = path3; - const alternatePaths = [path1, path2, path3, path4]; - const wrapper = mount( - - ); - validate(wrapper, code, 4, alternatePaths, selected); - wrapper.unmount(); - }); -}); - -describe('AlternatePathPanel', () => { - const TEST_SELECTED_PATH = new PathModel({ code: 'a', label: 'A', path: 'a/a' }); - const TEST_ALTERNATE_PATH = new PathModel({ code: 'a', label: 'A', path: 'b/a' }); - - function validate(wrapper: ReactWrapper, hasSelectedPath = false, alternatePathCount = 0): void { - expect(wrapper.find('.current-path-container')).toHaveLength(hasSelectedPath ? 1 : 0); - expect(wrapper.find('.alternate-paths-container')).toHaveLength(1); - expect(wrapper.find('.no-path-info')).toHaveLength(alternatePathCount === 0 ? 1 : 0); - expect(wrapper.find('.title')).toHaveLength(1 + (hasSelectedPath ? 1 : 0)); - expect(wrapper.find(ConceptPathDisplay)).toHaveLength(alternatePathCount + (hasSelectedPath ? 1 : 0)); - } - - test('no paths', () => { - const wrapper = mount( - - ); - validate(wrapper); - wrapper.unmount(); - }); - - test('with selected path but no alternates', () => { - const wrapper = mount( - - ); - validate(wrapper, true); - expect(wrapper.find(ConceptPathDisplay).prop('path')).toBe(TEST_SELECTED_PATH); - expect(wrapper.find(ConceptPathDisplay).prop('isSelected')).toBe(true); - wrapper.unmount(); - }); - - test('with selected path and alternate', () => { - const wrapper = mount( - - ); - validate(wrapper, true, 1); - expect(wrapper.find(ConceptPathDisplay).first().prop('path')).toBe(TEST_SELECTED_PATH); - expect(wrapper.find(ConceptPathDisplay).first().prop('isSelected')).toBe(true); - expect(wrapper.find(ConceptPathDisplay).first().prop('onClick')).toBeUndefined(); - expect(wrapper.find(ConceptPathDisplay).last().prop('path')).toBe(TEST_ALTERNATE_PATH); - expect(wrapper.find(ConceptPathDisplay).last().prop('isSelected')).toBeUndefined(); - expect(wrapper.find(ConceptPathDisplay).last().prop('onClick')).toBeDefined(); - wrapper.unmount(); - }); - - test('with no selected path and alternates', () => { - const wrapper = mount( - - ); - validate(wrapper, false, 2); - expect(wrapper.find(ConceptPathDisplay).first().prop('path')).toBe(TEST_SELECTED_PATH); - expect(wrapper.find(ConceptPathDisplay).first().prop('isSelected')).toBeUndefined(); - expect(wrapper.find(ConceptPathDisplay).first().prop('onClick')).toBeDefined(); - expect(wrapper.find(ConceptPathDisplay).last().prop('path')).toBe(TEST_ALTERNATE_PATH); - expect(wrapper.find(ConceptPathDisplay).last().prop('isSelected')).toBeUndefined(); - expect(wrapper.find(ConceptPathDisplay).last().prop('onClick')).toBeDefined(); - wrapper.unmount(); - }); -}); diff --git a/packages/components/src/internal/components/ontology/ConceptPathInfo.test.tsx b/packages/components/src/internal/components/ontology/ConceptPathInfo.test.tsx new file mode 100644 index 0000000000..df8c4f0db7 --- /dev/null +++ b/packages/components/src/internal/components/ontology/ConceptPathInfo.test.tsx @@ -0,0 +1,173 @@ +import React from 'react'; +import { render } from '@testing-library/react'; + +import { AlternatePathPanel, ConceptPathInfo, ConceptPathInfoImpl } from './ConceptPathInfo'; +import { PathModel } from './models'; +import { waitFor } from '@testing-library/dom'; + +jest.mock('./actions', () => ({ + ...jest.requireActual('./actions'), + fetchAlternatePaths: jest.fn().mockResolvedValue([]), + fetchParentPaths: jest.fn().mockResolvedValue([]), +})); + +const TEST_SELECTED_PATH = new PathModel({ code: 'a', label: 'A', path: 'a/a' }); +const TEST_ALTERNATE_PATH = new PathModel({ code: 'a', label: 'A', path: 'b/a' }); + +describe('ConceptPathInfo', () => { + test('Nothing set', () => { + const { container } = render(); + expect(container.querySelector('[role="alert"]')).toBeNull(); + expect(container.querySelector('.none-selected').textContent).toBe('No concept selected'); + }); +}); + +describe('ConceptPathInfoImpl', () => { + test('Nothing set', () => { + const { container } = render( + + ); + expect(container.querySelector('.none-selected').textContent).toBe('No concept selected'); + expect(container.querySelector('.concept-pathinfo-container')).toBeNull(); + }); + + test('Code set, aka Loading', () => { + const { container } = render( + + ); + expect(container.querySelector('.concept-pathinfo-container')).not.toBeNull(); + expect(container.querySelector('.fa-spinner')).not.toBeNull(); + expect(container.querySelector('.alternate-paths-container')).toBeNull(); + }); + + test('Loading, Selected path set, alternate paths undefined', () => { + const path = new PathModel({ path: 'abcd/efg/', label: 'first' }); + const { container } = render( + + ); + expect(container.querySelector('.concept-pathinfo-container')).not.toBeNull(); + expect(container.querySelector('.title').textContent).toBe('first'); + expect(container.querySelector('.fa-spinner')).not.toBeNull(); + }); + + test('Selected path set, alternate paths empty', () => { + const path = new PathModel({ path: 'abcd/efg/', label: 'first' }); + const { container } = render( + + ); + expect(container.querySelector('.concept-pathinfo-container')).not.toBeNull(); + expect(container.querySelector('.fa-spinner')).toBeNull(); + expect(container.querySelector('.no-path-info').textContent).toBe('No path information available'); + }); + + test('Only selected path', async () => { + const path = new PathModel({ path: 'abcd/efg/', label: 'first' }); + const { container } = render( + + ); + await waitFor(() => { + expect(container.querySelector('.current-path-container')).not.toBeNull(); + }); + expect(container.querySelector('.current-path-container .concept-path-container.selected')).not.toBeNull(); + expect(container.querySelector('.alternate-paths-container')).not.toBeNull(); + expect(container.querySelector('.alternate-paths-container .no-path-info').textContent).toBe( + 'No alternate paths' + ); + }); + + test('Selected path and alternate paths', async () => { + const path1 = new PathModel({ path: 'abcd/efg/', label: 'first' }); + const path2 = new PathModel({ path: '1234/efg/', label: 'second' }); + const path3 = new PathModel({ path: 'abcd/efg/123', label: 'third' }); + const path4 = new PathModel({ path: 'abcd/efg/4', label: 'fourth' }); + const { container } = render( + + ); + await waitFor(() => { + expect(container.querySelector('.current-path-container .concept-path-container.selected')).not.toBeNull(); + }); + expect(container.querySelector('.alternate-paths-container')).not.toBeNull(); + expect(container.querySelectorAll('.alternate-paths-container .concept-path-container')).toHaveLength(3); + expect(container.querySelector('.alternate-paths-container .no-path-info')).toBeNull(); + }); +}); + +describe('AlternatePathPanel', () => { + test('no paths', () => { + const { container } = render( + + ); + expect(container.querySelector('.current-path-container')).toBeNull(); + expect(container.querySelector('.alternate-paths-container')).not.toBeNull(); + expect(container.querySelector('.no-path-info').textContent).toBe('No alternate paths'); + }); + + test('with selected path but no alternates', async () => { + const { container } = render( + + ); + await waitFor(() => { + expect(container.querySelector('.current-path-container')).not.toBeNull(); + }); + expect(container.querySelector('.current-path-container .concept-path-container.selected')).not.toBeNull(); + expect(container.querySelector('.alternate-paths-container')).not.toBeNull(); + expect(container.querySelector('.alternate-paths-container .no-path-info').textContent).toBe( + 'No alternate paths' + ); + }); + + test('with selected path and alternate', async () => { + const { container } = render( + + ); + await waitFor(() => { + expect(container.querySelector('.current-path-container .concept-path-container.selected')).not.toBeNull(); + }); + expect(container.querySelector('.alternate-paths-container .no-path-info')).toBeNull(); + expect(container.querySelectorAll('.alternate-paths-container .concept-path-container')).toHaveLength(1); + }); + + test('with no selected path and alternates', async () => { + const { container } = render( + + ); + await waitFor(() => { + expect(container.querySelector('.current-path-container')).toBeNull(); + }); + expect(container.querySelector('.alternate-paths-container')).not.toBeNull(); + expect(container.querySelector('.alternate-paths-container .no-path-info')).toBeNull(); + expect(container.querySelectorAll('.alternate-paths-container .concept-path-container')).toHaveLength(2); + }); +}); diff --git a/packages/components/src/internal/components/ontology/OntologyBrowserFilterPanel.spec.tsx b/packages/components/src/internal/components/ontology/OntologyBrowserFilterPanel.spec.tsx deleted file mode 100644 index 6e4a05a48e..0000000000 --- a/packages/components/src/internal/components/ontology/OntologyBrowserFilterPanel.spec.tsx +++ /dev/null @@ -1,119 +0,0 @@ -import React from 'react'; - -import { Filter } from '@labkey/api'; -import { mount, ReactWrapper } from 'enzyme'; - -import { Alert } from '../base/Alert'; -import { waitForLifecycle } from '../../test/enzymeTestHelpers'; - -import { OntologyBrowserPanel } from './OntologyBrowserPanel'; -import { OntologyBrowserFilterPanel } from './OntologyBrowserFilterPanel'; -import { PathModel } from './models'; - -const DEFAULT_PROPS = { - ontologyId: 'TestOntology', - conceptSubtree: undefined, - filterValue: undefined, - filterType: undefined, - onFilterChange: jest.fn, -}; - -jest.mock('./actions.ts', () => { - // Require the original module to not be mocked... - const originalModule = jest.requireActual('./actions.ts'); - return { - ...originalModule, - fetchPathModel: jest.fn().mockReturnValue({ - path: 'root', - code: 'testroot', - label: 'test root', - hasChildren: false, - children: undefined, - } as PathModel), - }; -}); - -const EqStub = { getURLSuffix: () => 'eq' } as Filter.IFilterType; -const InSubtreeStub = { getURLSuffix: () => 'concept:insubtree' } as Filter.IFilterType; -const NotInSubtreeStub = { getURLSuffix: () => 'concept:notinsubtree' } as Filter.IFilterType; - -describe('OntologyBrowserFilterPanel', () => { - const validate = (wrapper: ReactWrapper): void => { - expect(wrapper.find(Alert)).toHaveLength(2); - expect(wrapper.find(Alert).first().text()).toBe(''); - expect(wrapper.find(OntologyBrowserPanel)).toHaveLength(1); - expect(wrapper.find(OntologyBrowserPanel).prop('hideConceptInfo')).toBeTruthy(); - }; - - test('default props', () => { - const wrapper = mount(); - validate(wrapper); - - wrapper.unmount(); - }); - - // change filter value - test('Concept filter value changed', async () => { - const changeHandler = jest.fn(); - const props = { - ...DEFAULT_PROPS, - filterValue: 'Test:Code', - filterType: EqStub, - onFilterChange: changeHandler, - }; - - const wrapper = mount(); - validate(wrapper); - expect(changeHandler).toHaveBeenCalledTimes(0); - - await waitForLifecycle(wrapper.setProps({ filterValue: 'Mock:Code2' })); - // Shouldn't call out to handler unless change is from the panel - expect(changeHandler).toHaveBeenCalledTimes(0); - expect(wrapper.find(OntologyBrowserPanel).prop('filters')).toHaveProperty('size', 1); - - // Multi valued filter - await waitForLifecycle(wrapper.setProps({ filterValue: 'Mock:Code2;Mock:Code3;Mock:Code1' })); - expect(changeHandler).toHaveBeenCalledTimes(0); - expect(wrapper.find(OntologyBrowserPanel).prop('filters')).toHaveProperty('size', 3); - expect(wrapper.find(OntologyBrowserPanel).prop('filters').get('Mock:Code3')).toHaveProperty( - 'code', - 'Mock:Code3' - ); - - // Path valued filter - await waitForLifecycle(wrapper.setProps({ filterValue: 'Mock:Code2/Mock:Code3/Mock:Code1' })); - expect(changeHandler).toHaveBeenCalledTimes(0); - expect(wrapper.find(OntologyBrowserPanel).prop('filters')).toHaveProperty('size', 1); - - wrapper.unmount(); - }); - - // change filter type - test('Concept filter type changed', async () => { - const changeHandler = jest.fn(); - const props = { - ...DEFAULT_PROPS, - filterValue: 'Test:Code', - filterType: EqStub, - onFilterChange: changeHandler, - }; - - const wrapper = mount(); - validate(wrapper); - expect(changeHandler).toHaveBeenCalledTimes(0); - - await waitForLifecycle(wrapper.setProps({ filterType: InSubtreeStub })); - // Shouldn't call out to handler unless change is from the panel - expect(changeHandler).toHaveBeenCalledTimes(0); - - // Multi valued filter - await waitForLifecycle(wrapper.setProps({ filterType: NotInSubtreeStub })); - expect(changeHandler).toHaveBeenCalledTimes(0); - - // Path valued filter - await waitForLifecycle(wrapper.setProps({ filterType: EqStub })); - expect(changeHandler).toHaveBeenCalledTimes(0); - - wrapper.unmount(); - }); -}); diff --git a/packages/components/src/internal/components/ontology/OntologyBrowserFilterPanel.test.tsx b/packages/components/src/internal/components/ontology/OntologyBrowserFilterPanel.test.tsx new file mode 100644 index 0000000000..5e59d1cb87 --- /dev/null +++ b/packages/components/src/internal/components/ontology/OntologyBrowserFilterPanel.test.tsx @@ -0,0 +1,42 @@ +import React from 'react'; +import { render } from '@testing-library/react'; + +import { OntologyBrowserFilterPanel } from './OntologyBrowserFilterPanel'; +import { PathModel } from './models'; + +jest.mock('./actions', () => ({ + ...jest.requireActual('./actions'), + fetchAlternatePaths: jest.fn().mockResolvedValue([]), + fetchChildPaths: jest.fn().mockResolvedValue({ children: [] }), + fetchConceptForCode: jest.fn().mockResolvedValue(undefined), + fetchParentPaths: jest.fn().mockResolvedValue([]), + fetchPathModel: jest.fn().mockResolvedValue({ + path: 'root', + code: 'testroot', + label: 'test root', + hasChildren: false, + children: undefined, + } as PathModel), + getOntologyDetails: jest.fn().mockResolvedValue(undefined), +})); + +jest.mock('../files/FileTree', () => ({ + DEFAULT_ROOT_PREFIX: '|root', + FileTree: () =>
, +})); + +const DEFAULT_PROPS = { + ontologyId: 'TestOntology', + conceptSubtree: undefined, + filterValue: undefined, + filterType: undefined, + onFilterChange: jest.fn(), +}; + +describe('OntologyBrowserFilterPanel', () => { + test('default props', () => { + const { container } = render(); + expect(container.querySelector('[role="alert"]')).toBeNull(); + expect(container.querySelector('.fa-spinner')).not.toBeNull(); + }); +}); diff --git a/packages/components/src/internal/components/ontology/OntologyBrowserModal.spec.tsx b/packages/components/src/internal/components/ontology/OntologyBrowserModal.spec.tsx deleted file mode 100644 index db524dd802..0000000000 --- a/packages/components/src/internal/components/ontology/OntologyBrowserModal.spec.tsx +++ /dev/null @@ -1,35 +0,0 @@ -import React from 'react'; -import { mount, ReactWrapper, shallow } from 'enzyme'; - -import { OntologyBrowserModal } from './OntologyBrowserModal'; -import { OntologyBrowserPanel } from './OntologyBrowserPanel'; - -const DEFAULT_PROPS = { - title: 'Test title', - onCancel: jest.fn(), - onApply: jest.fn(), -}; - -describe('OntologyBrowserModal', () => { - function validate(wrapper: ReactWrapper): void { - expect(wrapper.find('Modal').prop('bsSize')).toBe('lg'); - expect(wrapper.find('Modal').prop('onCancel')).toBe(DEFAULT_PROPS.onCancel); - expect(wrapper.find('.modal-title').text()).toBe(DEFAULT_PROPS.title); - expect(wrapper.find(OntologyBrowserPanel)).toHaveLength(1); - expect(wrapper.find('button')).toHaveLength(3); - } - - test('default props', () => { - const wrapper = mount(); - validate(wrapper); - wrapper.unmount(); - }); - - test('OntologyBrowserPanel props', () => { - const wrapper = shallow(); - const panel = wrapper.find(OntologyBrowserPanel); - expect(panel.prop('asPanel')).toBe(false); - expect(panel.prop('initOntologyId')).toBe('testOntId'); - wrapper.unmount(); - }); -}); diff --git a/packages/components/src/internal/components/ontology/OntologyBrowserModal.test.tsx b/packages/components/src/internal/components/ontology/OntologyBrowserModal.test.tsx new file mode 100644 index 0000000000..78b0037e03 --- /dev/null +++ b/packages/components/src/internal/components/ontology/OntologyBrowserModal.test.tsx @@ -0,0 +1,39 @@ +import React from 'react'; +import { render } from '@testing-library/react'; +import { waitFor } from '@testing-library/dom'; + +import { OntologyBrowserModal } from './OntologyBrowserModal'; + +jest.mock('./actions', () => ({ + ...jest.requireActual('./actions'), + fetchAlternatePaths: jest.fn().mockResolvedValue([]), + fetchChildPaths: jest.fn().mockResolvedValue({ children: [] }), + fetchConceptForCode: jest.fn().mockResolvedValue(undefined), + getOntologyDetails: jest.fn().mockResolvedValue(undefined), +})); + +const DEFAULT_PROPS = { + title: 'Test title', + onCancel: jest.fn(), + onApply: jest.fn(), +}; + +describe('OntologyBrowserModal', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + test('default props', () => { + render(); + expect(document.querySelector('.modal-title').textContent).toBe('Test title'); + expect(document.querySelectorAll('button')).toHaveLength(3); + }); + + test('OntologyBrowserPanel props', async () => { + render(); + const { getOntologyDetails } = jest.requireMock('./actions'); + await waitFor(() => { + expect(getOntologyDetails).toHaveBeenCalledWith('testOntId'); + }); + }); +}); diff --git a/packages/components/src/internal/components/ontology/OntologyBrowserPanel.spec.tsx b/packages/components/src/internal/components/ontology/OntologyBrowserPanel.spec.tsx deleted file mode 100644 index 1dfed966ca..0000000000 --- a/packages/components/src/internal/components/ontology/OntologyBrowserPanel.spec.tsx +++ /dev/null @@ -1,105 +0,0 @@ -import React from 'react'; -import { mount, ReactWrapper } from 'enzyme'; - -import { Alert } from '../base/Alert'; - -import { LabelHelpTip } from '../base/LabelHelpTip'; - -import { LoadingSpinner } from '../base/LoadingSpinner'; - -import { OntologyBrowserPanel, OntologyBrowserPanelImpl } from './OntologyBrowserPanel'; -import { OntologySelectionPanel } from './OntologySelectionPanel'; -import { OntologyTreeSearchContainer } from './OntologyTreeSearchContainer'; -import { OntologyTreePanel } from './OntologyTreePanel'; -import { ConceptInformationTabs } from './ConceptInformationTabs'; -import { ConceptModel, OntologyModel } from './models'; - -describe('OntologyBrowserPanel', () => { - function validate(wrapper: ReactWrapper, hasOntologyId: boolean): void { - expect(wrapper.find(Alert)).toHaveLength(hasOntologyId ? 1 : 2); - expect(wrapper.find(Alert).first().text()).toBe(''); - expect(wrapper.find(OntologySelectionPanel)).toHaveLength(!hasOntologyId ? 1 : 0); - expect(wrapper.find(OntologyBrowserPanelImpl)).toHaveLength(hasOntologyId ? 1 : 0); - } - - test('no initOntologyId', () => { - const wrapper = mount(); - validate(wrapper, false); - expect(wrapper.find(OntologySelectionPanel).prop('asPanel')).toBeTruthy(); - wrapper.unmount(); - }); - - test('with initOntologyId', () => { - const wrapper = mount(); - validate(wrapper, true); - expect(wrapper.find(OntologyBrowserPanelImpl).prop('asPanel')).toBeTruthy(); - wrapper.unmount(); - }); - - test('asPanel false', () => { - const wrapper = mount(); - validate(wrapper, false); - expect(wrapper.find(OntologySelectionPanel).prop('asPanel')).toBeFalsy(); - wrapper.unmount(); - }); -}); - -const DEFAULT_PROPS = { - ontology: undefined, - selectedConcept: undefined, - setSelectedConcept: jest.fn, - setSelectedPath: jest.fn, - asPanel: false, -}; - -const TEST_ONTOLOGY = new OntologyModel({ - abbreviation: 't', - name: 'test name', - conceptCount: 100, - description: 'test desc', -}); -const TEST_CONCEPT = new ConceptModel({ code: 'a', label: 'b' }); - -describe('OntologyBrowserPanelImpl', () => { - function validate(wrapper: ReactWrapper, loading: boolean, asPanel = false): void { - expect(wrapper.find('.ontology-browser-container')).toHaveLength(!loading ? 1 : 0); - expect(wrapper.find('.left-panel')).toHaveLength(!loading ? 1 : 0); - expect(wrapper.find('.right-panel')).toHaveLength(!loading ? 1 : 0); - expect(wrapper.find(OntologyTreeSearchContainer)).toHaveLength(!loading ? 1 : 0); - expect(wrapper.find(OntologyTreePanel)).toHaveLength(!loading ? 1 : 0); - expect(wrapper.find(ConceptInformationTabs)).toHaveLength(!loading ? 1 : 0); - expect(wrapper.find('.panel-body')).toHaveLength(asPanel ? 1 : 0); - expect(wrapper.find(LabelHelpTip)).toHaveLength(asPanel ? 1 : 0); - } - - test('loading', () => { - const wrapper = mount(); - validate(wrapper, true); - expect(wrapper.find(LoadingSpinner)).toHaveLength(1); - wrapper.unmount(); - }); - - test('ontology', () => { - const wrapper = mount(); - validate(wrapper, false); - expect(wrapper.find(OntologyTreeSearchContainer).prop('ontology')).toBe(TEST_ONTOLOGY); - expect(wrapper.find(OntologyTreePanel).prop('root').label).toBe(TEST_ONTOLOGY.name); - wrapper.unmount(); - }); - - test('selectedConcept', () => { - const wrapper = mount( - - ); - validate(wrapper, false); - expect(wrapper.find(ConceptInformationTabs).prop('concept')).toBe(TEST_CONCEPT); - wrapper.unmount(); - }); - - test('asPanel', () => { - const wrapper = mount(); - validate(wrapper, false, true); - expect(wrapper.find('.panel-heading').text()).toContain('Browse test name (t)'); - wrapper.unmount(); - }); -}); diff --git a/packages/components/src/internal/components/ontology/OntologyBrowserPanel.test.tsx b/packages/components/src/internal/components/ontology/OntologyBrowserPanel.test.tsx new file mode 100644 index 0000000000..4adaa771b8 --- /dev/null +++ b/packages/components/src/internal/components/ontology/OntologyBrowserPanel.test.tsx @@ -0,0 +1,104 @@ +import React from 'react'; +import { render } from '@testing-library/react'; +import { waitFor } from '@testing-library/dom'; + +import { OntologyBrowserPanel, OntologyBrowserPanelImpl } from './OntologyBrowserPanel'; +import { ConceptModel, OntologyModel } from './models'; + +jest.mock('./actions', () => ({ + ...jest.requireActual('./actions'), + fetchAlternatePaths: jest.fn().mockResolvedValue([]), + fetchChildPaths: jest.fn().mockResolvedValue({ children: [] }), + fetchConceptForCode: jest.fn().mockResolvedValue(undefined), + fetchParentPaths: jest.fn().mockResolvedValue([]), + getOntologyDetails: jest.fn().mockResolvedValue(undefined), +})); + +jest.mock('../files/FileTree', () => ({ + DEFAULT_ROOT_PREFIX: '|root', + FileTree: () =>
, +})); + +const TEST_ONTOLOGY = new OntologyModel({ + abbreviation: 't', + name: 'test name', + conceptCount: 100, + description: 'test desc', +}); +const TEST_CONCEPT = new ConceptModel({ code: 'a', label: 'b' }); + +describe('OntologyBrowserPanel', () => { + test('no initOntologyId', async () => { + const { container } = render(); + // Default asPanel=true, OntologySelectionPanel shown with asPanel=true + await waitFor(() => { + // After fetchChildPaths resolves with empty children, shows no-ontologies warning + expect(container.querySelector('.alert-warning')).not.toBeNull(); + }); + expect(container.querySelector('.ontology-browser-container')).not.toBeNull(); + }); + + test('with initOntologyId', () => { + const { container } = render(); + // OntologyBrowserPanelImpl renders, getOntologyDetails resolves with undefined => spinner stays + expect(container.querySelector('.fa-spinner')).not.toBeNull(); + }); + + test('asPanel false', async () => { + const { container } = render(); + await waitFor(() => { + expect(container.querySelector('.alert-warning')).not.toBeNull(); + }); + // OntologySelectionPanel rendered with asPanel=false => no panel container + expect(container.querySelector('.ontology-browser-container')).toBeNull(); + }); +}); + +const DEFAULT_IMPL_PROPS = { + ontology: undefined, + selectedConcept: undefined, + setSelectedPath: jest.fn(), + asPanel: false, +}; + +describe('OntologyBrowserPanelImpl', () => { + test('loading', () => { + const { container } = render(); + expect(container.querySelector('.fa-spinner')).not.toBeNull(); + expect(container.querySelector('.ontology-browser-container')).toBeNull(); + }); + + test('ontology', () => { + const { container } = render( + + ); + expect(container.querySelector('.ontology-browser-container')).not.toBeNull(); + expect(container.querySelector('.left-panel')).not.toBeNull(); + expect(container.querySelector('.right-panel')).not.toBeNull(); + expect(container.querySelector('.concept-search-container')).not.toBeNull(); + expect(container.querySelector('.mock-file-tree')).not.toBeNull(); + expect(container.querySelector('.panel-body')).toBeNull(); + }); + + test('selectedConcept', () => { + const { container } = render( + + ); + expect(container.querySelector('.ontology-browser-container')).not.toBeNull(); + expect(container.querySelector('.left-panel')).not.toBeNull(); + expect(container.querySelector('.right-panel')).not.toBeNull(); + }); + + test('asPanel', () => { + const { container } = render( + + ); + expect(container.querySelector('.ontology-browser-container')).not.toBeNull(); + expect(container.querySelector('.panel-body')).not.toBeNull(); + expect(container.querySelector('.panel-heading').textContent).toContain('Browse test name (t)'); + }); +}); diff --git a/packages/components/src/internal/components/ontology/OntologyConceptAnnotation.spec.tsx b/packages/components/src/internal/components/ontology/OntologyConceptAnnotation.spec.tsx deleted file mode 100644 index 1c3e9a935f..0000000000 --- a/packages/components/src/internal/components/ontology/OntologyConceptAnnotation.spec.tsx +++ /dev/null @@ -1,78 +0,0 @@ -import React from 'react'; -import { mount, ReactWrapper } from 'enzyme'; - -import { DOMAIN_FIELD_FULLY_LOCKED } from '../domainproperties/constants'; - -import { DomainField } from '../domainproperties/models'; - -import { DomainFieldLabel } from '../domainproperties/DomainFieldLabel'; - -import { OntologyConceptAnnotation } from './OntologyConceptAnnotation'; -import { OntologyBrowserModal } from './OntologyBrowserModal'; - -const DEFAULT_PROPS = { - id: 'testId', - field: new DomainField(), - onChange: jest.fn, - error: undefined, -}; - -const TEST_FIELD = new DomainField({ principalConceptCode: 'code:123' }); - -describe('OntologyConceptAnnotation', () => { - function validate(wrapper: ReactWrapper, hasCode: boolean, canRemove = true): void { - expect(wrapper.find(DomainFieldLabel)).toHaveLength(1); - expect(wrapper.find('.domain-annotation-table')).toHaveLength(1); - expect(wrapper.find('.domain-validation-button')).toHaveLength(1); - expect(getSelectButton(wrapper).text()).toBe('Select Concept'); - expect(wrapper.find('.domain-text-label')).toHaveLength(!hasCode || !canRemove ? 1 : 0); - if (!hasCode) { - expect(wrapper.find('.domain-text-label').text()).toBe('None Set'); - } - - expect(wrapper.find('.content')).toHaveLength(hasCode && canRemove ? 3 : 2); - expect(wrapper.find('.domain-annotation-item')).toHaveLength(hasCode ? 1 : 0); - } - - function getSelectButton(wrapper: ReactWrapper): ReactWrapper { - return wrapper.find('.domain-validation-button').first(); - } - - test('no principalConceptCode', () => { - const wrapper = mount(); - validate(wrapper, false); - expect(wrapper.find('.domain-text-label').text()).toBe('None Set'); - wrapper.unmount(); - }); - - test('principalConceptCode', () => { - const wrapper = mount(); - validate(wrapper, true); - expect(wrapper.find('.domain-annotation-item').text()).toBe(TEST_FIELD.principalConceptCode); - expect(wrapper.find('.fa-remove')).toHaveLength(1); - expect(getSelectButton(wrapper).prop('disabled')).toBeFalsy(); - wrapper.unmount(); - }); - - test('isFieldLocked and select button props', () => { - const field = TEST_FIELD.merge({ lockType: DOMAIN_FIELD_FULLY_LOCKED }) as DomainField; - const wrapper = mount(); - validate(wrapper, true, false); - expect(wrapper.find('.fa-remove')).toHaveLength(0); - expect(getSelectButton(wrapper).prop('disabled')).toBeTruthy(); - expect(getSelectButton(wrapper).prop('id')).toBe(DEFAULT_PROPS.id); - expect(getSelectButton(wrapper).prop('name')).toBe('domainpropertiesrow-principalConceptCode'); - wrapper.unmount(); - }); - - test('showSelectModal', () => { - const wrapper = mount(); - validate(wrapper, true); - expect(wrapper.find(OntologyBrowserModal)).toHaveLength(0); - getSelectButton(wrapper).simulate('click'); - expect(wrapper.find(OntologyBrowserModal)).toHaveLength(1); - expect(wrapper.find(OntologyBrowserModal).prop('title')).toBe('Select Concept'); - expect(wrapper.find(OntologyBrowserModal).prop('initOntologyId')).toBe(TEST_FIELD.sourceOntology); - wrapper.unmount(); - }); -}); diff --git a/packages/components/src/internal/components/ontology/OntologyConceptAnnotation.test.tsx b/packages/components/src/internal/components/ontology/OntologyConceptAnnotation.test.tsx new file mode 100644 index 0000000000..73a113ccab --- /dev/null +++ b/packages/components/src/internal/components/ontology/OntologyConceptAnnotation.test.tsx @@ -0,0 +1,68 @@ +import React from 'react'; +import { fireEvent, render } from '@testing-library/react'; +import { waitFor } from '@testing-library/dom'; + +import { DOMAIN_FIELD_FULLY_LOCKED } from '../domainproperties/constants'; +import { DomainField } from '../domainproperties/models'; + +import { OntologyConceptAnnotation } from './OntologyConceptAnnotation'; + +jest.mock('./actions', () => ({ + ...jest.requireActual('./actions'), + fetchAlternatePaths: jest.fn().mockResolvedValue([]), + fetchConceptForCode: jest.fn().mockResolvedValue(undefined), + fetchPathModel: jest.fn().mockResolvedValue(undefined), + getOntologyDetails: jest.fn().mockResolvedValue(undefined), +})); + +const DEFAULT_PROPS = { + id: 'testId', + field: new DomainField(), + onChange: jest.fn(), +}; + +const TEST_FIELD = new DomainField({ principalConceptCode: 'code:123' }); + +describe('OntologyConceptAnnotation', () => { + test('no principalConceptCode', () => { + const { container } = render(); + expect(container.querySelector('.domain-annotation-table')).not.toBeNull(); + expect(container.querySelector('.domain-validation-button').textContent).toBe('Select Concept'); + expect(container.querySelector('.domain-text-label').textContent).toBe('None Set'); + expect(container.querySelector('.domain-annotation-item')).toBeNull(); + }); + + test('principalConceptCode', async () => { + const { container } = render(); + await waitFor(() => { + expect(container.querySelector('.domain-annotation-item').textContent).toBe( + TEST_FIELD.principalConceptCode + ); + }); + expect(container.querySelector('.domain-text-label')).toBeNull(); + expect(container.querySelector('.fa-remove')).not.toBeNull(); + expect((container.querySelector('.domain-validation-button') as HTMLButtonElement).disabled).toBe(false); + }); + + test('isFieldLocked', async () => { + const field = TEST_FIELD.merge({ lockType: DOMAIN_FIELD_FULLY_LOCKED }) as DomainField; + const { container } = render(); + await waitFor(() => { + expect(container.querySelector('.domain-annotation-item.domain-text-label')).not.toBeNull(); + }); + const button = container.querySelector('.domain-validation-button') as HTMLButtonElement; + expect(button.disabled).toBe(true); + expect(button.id).toBe(DEFAULT_PROPS.id); + expect(button.name).toBe('domainpropertiesrow-principalConceptCode'); + expect(container.querySelector('.fa-remove')).toBeNull(); + }); + + test('showSelectModal', async () => { + const { container } = render(); + expect(document.querySelector('.modal-title')).toBeNull(); + fireEvent.click(container.querySelector('.domain-validation-button')); + await waitFor(() => { + expect(document.querySelector('.modal-title').textContent).toBe('Select Concept'); + }); + }); +}); diff --git a/packages/components/src/internal/components/ontology/OntologyConceptSelectButton.spec.tsx b/packages/components/src/internal/components/ontology/OntologyConceptSelectButton.spec.tsx deleted file mode 100644 index 67be8d71e0..0000000000 --- a/packages/components/src/internal/components/ontology/OntologyConceptSelectButton.spec.tsx +++ /dev/null @@ -1,124 +0,0 @@ -import React from 'react'; -import { mount, ReactWrapper } from 'enzyme'; - -import { DomainField } from '../domainproperties/models'; - -import { DOMAIN_FIELD_FULLY_LOCKED } from '../domainproperties/constants'; - -import { OntologyConceptSelectButton } from './OntologyConceptSelectButton'; -import { ConceptOverviewTooltip } from './ConceptOverviewPanel'; -import { OntologyBrowserModal } from './OntologyBrowserModal'; - -const DEFAULT_PROPS = { - id: 'test-id', - title: 'Button Title', - field: new DomainField({}), - valueProp: 'principalConceptCode', - valueIsPath: false, - onChange: jest.fn, -}; - -describe('OntologyConceptSelectButton', () => { - function validate(wrapper: ReactWrapper, value = 'None Set', isFieldLocked = false, showModal = false): void { - const hasValue = value !== 'None Set'; - - expect(wrapper.find('.domain-annotation-table')).toHaveLength(1); - expect(wrapper.find(ConceptOverviewTooltip)).toHaveLength(1); - expect(wrapper.find(OntologyBrowserModal)).toHaveLength(showModal ? 1 : 0); - - expect(wrapper.find('button')).toHaveLength(showModal ? 4 : 1); - expect(wrapper.find('button').first().prop('disabled')).toBe(isFieldLocked); - expect(wrapper.find('button').first().text()).toBe('Button Title'); - - expect(wrapper.find('.fa-remove')).toHaveLength(hasValue && !isFieldLocked ? 1 : 0); - expect(wrapper.find('.domain-text-label')).toHaveLength(!hasValue || isFieldLocked ? 1 : 0); - expect(wrapper.find('.domain-annotation-item')).toHaveLength(hasValue ? 1 : 0); - if (hasValue) { - expect(wrapper.find('.domain-annotation-item').text()).toBe(value); - const itemOnClick = wrapper.find('.domain-annotation-item').prop('onClick'); - if (!isFieldLocked) expect(itemOnClick).toBeDefined(); - if (isFieldLocked) expect(itemOnClick).toBeUndefined(); - } else { - expect(wrapper.find('.domain-text-label').text()).toBe(value); - } - } - - test('no value set', () => { - const wrapper = mount(); - validate(wrapper); - wrapper.unmount(); - }); - - test('showSelectModal', () => { - const wrapper = mount(); - validate(wrapper); - wrapper.find('button').simulate('click'); - validate(wrapper, 'None Set', false, true); - wrapper.unmount(); - }); - - test('with value set', () => { - const wrapper = mount( - - ); - validate(wrapper, 'TEST VALUE'); - wrapper.unmount(); - }); - - test('isFieldLocked', () => { - const wrapper = mount( - - ); - validate(wrapper, 'TEST VALUE', true); - wrapper.unmount(); - }); - - test('OntologyBrowserModal props', () => { - const wrapper = mount( - - ); - wrapper.find('button').simulate('click'); - validate(wrapper, 'None Set', false, true); - const modal = wrapper.find(OntologyBrowserModal); - expect(modal.prop('title')).toBe('Button Title'); - expect(modal.prop('initOntologyId')).toBe(undefined); - wrapper.unmount(); - }); - - test('useFieldSourceOntology', () => { - const wrapper = mount( - - ); - wrapper.find('button').simulate('click'); - validate(wrapper, 'None Set', false, true); - const modal = wrapper.find(OntologyBrowserModal); - expect(modal.prop('initOntologyId')).toBe('Test Source'); - wrapper.unmount(); - }); -}); diff --git a/packages/components/src/internal/components/ontology/OntologyConceptSelectButton.test.tsx b/packages/components/src/internal/components/ontology/OntologyConceptSelectButton.test.tsx new file mode 100644 index 0000000000..ce073f711b --- /dev/null +++ b/packages/components/src/internal/components/ontology/OntologyConceptSelectButton.test.tsx @@ -0,0 +1,111 @@ +import React from 'react'; +import { fireEvent, render } from '@testing-library/react'; +import { waitFor } from '@testing-library/dom'; + +import { DOMAIN_FIELD_FULLY_LOCKED } from '../domainproperties/constants'; +import { DomainField } from '../domainproperties/models'; + +import { OntologyConceptSelectButton } from './OntologyConceptSelectButton'; + +jest.mock('./actions', () => ({ + ...jest.requireActual('./actions'), + fetchAlternatePaths: jest.fn().mockResolvedValue([]), + fetchChildPaths: jest.fn().mockResolvedValue(undefined), + fetchConceptForCode: jest.fn().mockResolvedValue(undefined), + fetchPathModel: jest.fn().mockResolvedValue(undefined), + getOntologyDetails: jest.fn().mockResolvedValue(undefined), +})); + +const DEFAULT_PROPS = { + id: 'test-id', + title: 'Button Title', + field: new DomainField({}), + valueProp: 'principalConceptCode', + valueIsPath: false, + onChange: jest.fn(), +}; + +describe('OntologyConceptSelectButton', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + test('no value set', () => { + const { container } = render(); + expect(container.querySelector('.domain-annotation-table')).not.toBeNull(); + const button = container.querySelector('button') as HTMLButtonElement; + expect(button.textContent).toBe('Button Title'); + expect(button.disabled).toBe(false); + expect(container.querySelector('.fa-remove')).toBeNull(); + expect(container.querySelector('.domain-text-label').textContent).toBe('None Set'); + expect(container.querySelector('.domain-annotation-item')).toBeNull(); + }); + + test('showSelectModal', async () => { + const { container } = render(); + expect(document.querySelector('.modal-title')).toBeNull(); + fireEvent.click(container.querySelector('button')); + await waitFor(() => { + expect(document.querySelector('.modal-title').textContent).toBe('Button Title'); + }); + }); + + test('with value set', async () => { + const { container } = render( + + ); + await waitFor(() => { + expect(container.querySelector('.domain-annotation-item').textContent).toBe('TEST VALUE'); + }); + expect(container.querySelector('.domain-text-label')).toBeNull(); + expect(container.querySelector('.fa-remove')).not.toBeNull(); + }); + + test('isFieldLocked', async () => { + const { container } = render( + + ); + await waitFor(() => { + expect(container.querySelector('.domain-annotation-item.domain-text-label')).not.toBeNull(); + }); + expect((container.querySelector('button') as HTMLButtonElement).disabled).toBe(true); + expect(container.querySelector('.domain-annotation-item').textContent).toBe('TEST VALUE'); + expect(container.querySelector('.fa-remove')).toBeNull(); + }); + + test('OntologyBrowserModal props', async () => { + const { container } = render( + + ); + fireEvent.click(container.querySelector('button')); + await waitFor(() => { + expect(document.querySelector('.modal-title')).not.toBeNull(); + }); + const { getOntologyDetails } = jest.requireMock('./actions'); + expect(getOntologyDetails).not.toHaveBeenCalled(); + }); + + test('useFieldSourceOntology', async () => { + const { container } = render( + + ); + fireEvent.click(container.querySelector('button')); + const { getOntologyDetails } = jest.requireMock('./actions'); + await waitFor(() => { + expect(getOntologyDetails).toHaveBeenCalledWith('Test Source'); + }); + }); +}); diff --git a/packages/components/src/internal/components/ontology/OntologyLookupOptions.spec.tsx b/packages/components/src/internal/components/ontology/OntologyLookupOptions.spec.tsx deleted file mode 100644 index aeb3fadf52..0000000000 --- a/packages/components/src/internal/components/ontology/OntologyLookupOptions.spec.tsx +++ /dev/null @@ -1,160 +0,0 @@ -import React from 'react'; -import { mount, ReactWrapper } from 'enzyme'; -import { List } from 'immutable'; - -import { SectionHeading } from '../domainproperties/SectionHeading'; -import { INTEGER_TYPE, ONTOLOGY_LOOKUP_TYPE, TEXT_TYPE } from '../domainproperties/PropDescType'; -import { DOMAIN_FIELD_FULLY_LOCKED } from '../domainproperties/constants'; - -import { DomainField } from '../domainproperties/models'; - -import { getDomainPropertiesTestAPIWrapper } from '../domainproperties/APIWrapper'; - -import { waitForLifecycle } from '../../test/enzymeTestHelpers'; - -import { OntologyLookupOptions } from './OntologyLookupOptions'; -import { OntologyConceptSelectButton } from './OntologyConceptSelectButton'; -import { OntologyModel } from './models'; - -const field1 = DomainField.create({ - name: 'field1', - conceptURI: ONTOLOGY_LOOKUP_TYPE.conceptURI, - rangeURI: ONTOLOGY_LOOKUP_TYPE.rangeURI, - sourceOntology: 'NCIT', - conceptImportColumn: 'field2', - conceptLabelColumn: 'field3', -}); -const field2 = DomainField.create({ - name: 'field2', // conceptImportColumn - rangeURI: TEXT_TYPE.rangeURI, -}); -const field3 = DomainField.create({ - name: 'field3', // conceptLabelColumn - rangeURI: TEXT_TYPE.rangeURI, -}); -const field4 = DomainField.create({ - name: 'field4', // other text field that should show as option - rangeURI: TEXT_TYPE.rangeURI, -}); -const field5 = DomainField.create({ - name: 'field5', // int field should not show as option - rangeURI: INTEGER_TYPE.rangeURI, -}); -const field6 = DomainField.create({ - name: '', // invalid name field should not show as option - rangeURI: TEXT_TYPE.rangeURI, -}); - -describe('OntologyLookupOptions', () => { - function getDefaultProps() { - return { - domainContainerPath: '/Where/The/Domain/Lives', - index: 0, - domainIndex: 0, - label: 'Test', - lockType: undefined, - onChange: jest.fn(), - onMultiChange: jest.fn(), - api: getDomainPropertiesTestAPIWrapper(jest.fn, { - fetchOntologies: jest.fn().mockResolvedValue([ - new OntologyModel({ - rowId: 2, - name: "Test HOM-UCARE-->\">'>'\"", - abbreviation: '45887', - }), - new OntologyModel({ - rowId: 1, - name: 'Test National Cancer Institute Thesaurus', - abbreviation: 'NCIT', - }), - ]), - }), - }; - } - - function validate( - wrapper: ReactWrapper, - disabled: boolean, - selectedSource: string, - importOptions: string[], - labelOptions: string[] - ): void { - expect(wrapper.find(SectionHeading)).toHaveLength(1); - expect(wrapper.find('.domain-field-label')).toHaveLength(4); - - const selectInputs = wrapper.find('select'); - expect(selectInputs).toHaveLength(3); - - // source ontology select - let selectInput = selectInputs.at(0); - expect(selectInput.prop('disabled')).toBe(disabled); - expect(selectInput.prop('value')).toBe(selectedSource); - let options = selectInput.children(); - expect(options).toHaveLength(2); - expect(options.find({ value: '45887' })).toHaveLength(1); - expect(options.find({ value: 'NCIT' })).toHaveLength(1); - - // import field select - selectInput = selectInputs.at(1); - expect(selectInput.prop('disabled')).toBe(disabled); - options = selectInput.children(); - expect(options).toHaveLength(importOptions.length); - importOptions.forEach(value => { - expect(options.find({ value })).toHaveLength(1); - }); - - // label field select - selectInput = selectInputs.at(2); - expect(selectInput.prop('disabled')).toBe(disabled); - options = selectInput.children(); - expect(options).toHaveLength(labelOptions.length); - labelOptions.forEach(value => { - expect(options.find({ value })).toHaveLength(1); - }); - - const conceptSelectBtn = wrapper.find(OntologyConceptSelectButton); - expect(conceptSelectBtn.prop('valueProp')).toBe('conceptSubtree'); - expect(conceptSelectBtn.prop('valueIsPath')).toBe(true); - expect(conceptSelectBtn.prop('useFieldSourceOntology')).toBe(true); - } - - test('default props', async () => { - const field = DomainField.create({}); - const domainFields = List.of(field); - - const wrapper = mount( - - ); - await waitForLifecycle(wrapper); - - validate(wrapper, false, undefined, [null], [null]); - wrapper.unmount(); - }); - - test('with additional fields and ontology field props', async () => { - const domainFields = List.of(field1, field2, field3, field4, field5, field6); - const wrapper = mount( - - ); - await waitForLifecycle(wrapper); - - validate(wrapper, false, 'NCIT', [null, 'field2', 'field4'], [null, 'field3', 'field4']); - wrapper.unmount(); - }); - - test('disabled selects', async () => { - const domainFields = List.of(field1, field2, field3, field4, field5, field6); - const wrapper = mount( - - ); - await waitForLifecycle(wrapper); - - validate(wrapper, true, 'NCIT', [null, 'field2', 'field4'], [null, 'field3', 'field4']); - wrapper.unmount(); - }); -}); diff --git a/packages/components/src/internal/components/ontology/OntologyLookupOptions.test.tsx b/packages/components/src/internal/components/ontology/OntologyLookupOptions.test.tsx new file mode 100644 index 0000000000..6854821f79 --- /dev/null +++ b/packages/components/src/internal/components/ontology/OntologyLookupOptions.test.tsx @@ -0,0 +1,127 @@ +import React from 'react'; +import { render } from '@testing-library/react'; +import { waitFor } from '@testing-library/dom'; +import { List } from 'immutable'; + +import { INTEGER_TYPE, ONTOLOGY_LOOKUP_TYPE, TEXT_TYPE } from '../domainproperties/PropDescType'; +import { DOMAIN_FIELD_FULLY_LOCKED } from '../domainproperties/constants'; +import { DomainField } from '../domainproperties/models'; +import { getDomainPropertiesTestAPIWrapper } from '../domainproperties/APIWrapper'; + +import { OntologyLookupOptions } from './OntologyLookupOptions'; +import { OntologyModel } from './models'; + +const field1 = DomainField.create({ + name: 'field1', + conceptURI: ONTOLOGY_LOOKUP_TYPE.conceptURI, + rangeURI: ONTOLOGY_LOOKUP_TYPE.rangeURI, + sourceOntology: 'NCIT', + conceptImportColumn: 'field2', + conceptLabelColumn: 'field3', +}); +const field2 = DomainField.create({ name: 'field2', rangeURI: TEXT_TYPE.rangeURI }); +const field3 = DomainField.create({ name: 'field3', rangeURI: TEXT_TYPE.rangeURI }); +const field4 = DomainField.create({ name: 'field4', rangeURI: TEXT_TYPE.rangeURI }); +const field5 = DomainField.create({ name: 'field5', rangeURI: INTEGER_TYPE.rangeURI }); +const field6 = DomainField.create({ name: '', rangeURI: TEXT_TYPE.rangeURI }); + +function getDefaultProps() { + return { + domainContainerPath: '/Where/The/Domain/Lives', + index: 0, + domainIndex: 0, + label: 'Test', + lockType: undefined, + onChange: jest.fn(), + onMultiChange: jest.fn(), + api: getDomainPropertiesTestAPIWrapper(jest.fn, { + fetchOntologies: jest.fn().mockResolvedValue([ + new OntologyModel({ + rowId: 2, + name: "Test HOM-UCARE-->\">'>'\"", + abbreviation: '45887', + }), + new OntologyModel({ + rowId: 1, + name: 'Test National Cancer Institute Thesaurus', + abbreviation: 'NCIT', + }), + ]), + }), + }; +} + +async function validateSelects( + container: HTMLElement, + disabled: boolean, + importOptionCount: number, + labelOptionCount: number, + importOptionValues: string[], + labelOptionValues: string[] +): Promise { + await waitFor(() => { + const selects = container.querySelectorAll('select'); + expect(selects).toHaveLength(3); + // Source ontology select should have 2 options (the 2 mocked ontologies) + expect(selects[0].querySelectorAll('option')).toHaveLength(2); + }); + + const selects = container.querySelectorAll('select'); + + // source ontology select + expect((selects[0] as HTMLSelectElement).disabled).toBe(disabled); + expect(selects[0].querySelector('option[value="45887"]')).not.toBeNull(); + expect(selects[0].querySelector('option[value="NCIT"]')).not.toBeNull(); + + // import field select + expect((selects[1] as HTMLSelectElement).disabled).toBe(disabled); + expect(selects[1].querySelectorAll('option')).toHaveLength(importOptionCount); + importOptionValues.forEach(value => { + if (value !== null) { + expect(selects[1].querySelector(`option[value="${value}"]`)).not.toBeNull(); + } + }); + + // label field select + expect((selects[2] as HTMLSelectElement).disabled).toBe(disabled); + expect(selects[2].querySelectorAll('option')).toHaveLength(labelOptionCount); + labelOptionValues.forEach(value => { + if (value !== null) { + expect(selects[2].querySelector(`option[value="${value}"]`)).not.toBeNull(); + } + }); +} + +describe('OntologyLookupOptions', () => { + test('default props', async () => { + const field = DomainField.create({}); + const domainFields = List.of(field); + const { container } = render( + + ); + await validateSelects(container, false, 1, 1, [null], [null]); + expect(container.querySelector('.domain-field-section-heading')).not.toBeNull(); + expect(container.querySelectorAll('.domain-field-label')).toHaveLength(4); + }); + + test('with additional fields and ontology field props', async () => { + const domainFields = List.of(field1, field2, field3, field4, field5, field6); + const { container } = render( + + ); + await validateSelects(container, false, 3, 3, [null, 'field2', 'field4'], [null, 'field3', 'field4']); + }); + + test('disabled selects', async () => { + const domainFields = List.of(field1, field2, field3, field4, field5, field6); + const { container } = render( + + ); + await validateSelects(container, true, 3, 3, [null, 'field2', 'field4'], [null, 'field3', 'field4']); + }); +}); diff --git a/packages/components/src/internal/components/ontology/OntologySelectionPanel.spec.tsx b/packages/components/src/internal/components/ontology/OntologySelectionPanel.spec.tsx deleted file mode 100644 index 56c1c0b7e6..0000000000 --- a/packages/components/src/internal/components/ontology/OntologySelectionPanel.spec.tsx +++ /dev/null @@ -1,71 +0,0 @@ -import React from 'react'; -import { mount, ReactWrapper } from 'enzyme'; - -import { Alert } from '../base/Alert'; -import { LoadingSpinner } from '../base/LoadingSpinner'; -import { SelectInput } from '../forms/input/SelectInput'; - -import { PathModel } from './models'; -import { OntologySelectionPanelImpl } from './OntologySelectionPanel'; - -const DEFAULT_PROPS = { - error: undefined, - ontologies: undefined, - asPanel: false, - onOntologySelection: jest.fn, -}; - -describe('OntologySelectionPanel', () => { - function validate(wrapper: ReactWrapper, ontCount?: number, errorTxt?: string, asPanel = false): void { - expect(wrapper.find(Alert).first().text()).toBe(errorTxt ?? ''); - expect(wrapper.find(LoadingSpinner)).toHaveLength(ontCount === undefined ? 1 : 0); - expect(wrapper.find('.alert-warning')).toHaveLength(ontCount !== undefined && ontCount === 0 ? 1 : 0); - expect(wrapper.find(SelectInput)).toHaveLength(ontCount !== undefined ? 1 : 0); - expect(wrapper.find('.ontology-browser-container')).toHaveLength(asPanel ? 1 : 0); - } - - test('loading', () => { - const wrapper = mount(); - validate(wrapper); - wrapper.unmount(); - }); - - test('no ontologies, non root admin', () => { - const ontologies = []; - const wrapper = mount(); - validate(wrapper, 0); - expect(wrapper.find('.alert-warning').text()).toBe('No ontologies have been loaded for this server.'); - expect(wrapper.find(SelectInput).prop('options')).toBe(ontologies); - wrapper.unmount(); - }); - - test('no ontologies, as root admin', () => { - LABKEY.user.isRootAdmin = true; - const ontologies = []; - const wrapper = mount(); - validate(wrapper, 0); - expect(wrapper.find('.alert-warning').text()).toContain('Click here to get started.'); - wrapper.unmount(); - }); - - test('with ontologies', () => { - const ontologies = [new PathModel()]; - const wrapper = mount(); - validate(wrapper, ontologies.length); - expect(wrapper.find(SelectInput).prop('options')).toBe(ontologies); - wrapper.unmount(); - }); - - test('asPanel', () => { - const wrapper = mount(); - validate(wrapper, 0, undefined, true); - expect(wrapper.find('.panel-body')).toHaveLength(1); - wrapper.unmount(); - }); - - test('error', () => { - const wrapper = mount(); - validate(wrapper, 0, 'test error'); - wrapper.unmount(); - }); -}); diff --git a/packages/components/src/internal/components/ontology/OntologySelectionPanel.test.tsx b/packages/components/src/internal/components/ontology/OntologySelectionPanel.test.tsx new file mode 100644 index 0000000000..5ab4b4b3a3 --- /dev/null +++ b/packages/components/src/internal/components/ontology/OntologySelectionPanel.test.tsx @@ -0,0 +1,66 @@ +import React from 'react'; +import { render } from '@testing-library/react'; + +import { PathModel } from './models'; +import { OntologySelectionPanelImpl } from './OntologySelectionPanel'; + +const DEFAULT_PROPS = { + error: undefined, + ontologies: undefined, + asPanel: false, + onOntologySelection: jest.fn(), +}; + +describe('OntologySelectionPanel', () => { + afterEach(() => { + LABKEY.user.isRootAdmin = false; + }); + + test('loading', () => { + const { container } = render(); + expect(container.querySelector('.fa-spinner')).not.toBeNull(); + expect(container.querySelector('.alert-warning')).toBeNull(); + expect(container.querySelector('[role="alert"]')).toBeNull(); + expect(container.querySelector('.ontology-browser-container')).toBeNull(); + }); + + test('no ontologies, non root admin', () => { + const { container } = render(); + expect(container.querySelector('.fa-spinner')).toBeNull(); + expect(container.querySelector('.alert-warning')).not.toBeNull(); + expect(container.querySelector('.alert-warning').textContent).toBe( + 'No ontologies have been loaded for this server.' + ); + }); + + test('no ontologies, as root admin', () => { + LABKEY.user.isRootAdmin = true; + const { container } = render(); + expect(container.querySelector('.alert-warning')).not.toBeNull(); + expect(container.querySelector('.alert-warning').textContent).toContain('Click here to get started.'); + }); + + test('with ontologies', () => { + const { container } = render( + + ); + expect(container.querySelector('.fa-spinner')).toBeNull(); + expect(container.querySelector('.alert-warning')).toBeNull(); + }); + + test('asPanel', () => { + const { container } = render( + + ); + expect(container.querySelector('.ontology-browser-container')).not.toBeNull(); + expect(container.querySelector('.panel-body')).not.toBeNull(); + }); + + test('error', () => { + const { container } = render( + + ); + expect(container.querySelector('[role="alert"]')).not.toBeNull(); + expect(container.querySelector('[role="alert"]').textContent).toBe('test error'); + }); +}); diff --git a/packages/components/src/internal/components/ontology/OntologyTreePanel.spec.tsx b/packages/components/src/internal/components/ontology/OntologyTreePanel.spec.tsx deleted file mode 100644 index a1d62f9c75..0000000000 --- a/packages/components/src/internal/components/ontology/OntologyTreePanel.spec.tsx +++ /dev/null @@ -1,77 +0,0 @@ -import React from 'react'; -import { mount } from 'enzyme'; - -import { FileTree } from '../files/FileTree'; - -import { FilterIcon, OntologyTreePanel } from './OntologyTreePanel'; -import { PathModel } from './models'; - -const DEFAULT_PROPS = { - root: new PathModel({ label: 'test label' }), - onNodeSelection: jest.fn, -}; - -describe('OntologyTreePanel', () => { - test('default props', () => { - const wrapper = mount(); - const fileTree = wrapper.find(FileTree); - expect(fileTree.prop('allowMultiSelect')).toBe(false); - expect(fileTree.prop('showNodeIcon')).toBe(false); - expect(fileTree.prop('defaultRootName')).toBe(DEFAULT_PROPS.root.label); - expect(fileTree.prop('showLoading')).toBe(false); - expect(fileTree.prop('showAnimations')).toBe(false); - wrapper.unmount(); - }); -}); - -const DEFAULT_FILTER_ICON_PROPS = { - node: undefined, - onClick: undefined, - filters: undefined, -}; - -describe('FilterIcon', () => { - test('default props', () => { - const wrapper = mount(); - const icon = wrapper.find('i'); - expect(icon.prop('className')).toBe('fa fa-filter'); - wrapper.unmount(); - }); - - test('node selected', () => { - const testnode = { data: { code: 'test' } }; - const testFilters = new Map().set('test', new PathModel()); - - const wrapper = mount(); - const icon = wrapper.find('i'); - expect(icon.prop('className')).toBe('fa fa-filter selected'); - expect(icon.prop('title')).toBe('Remove filter'); - wrapper.unmount(); - }); - - test('node not selected', () => { - const testnode = { data: { code: 'test' } }; - const testFilters = new Map().set('nope', new PathModel()); - - const wrapper = mount(); - const icon = wrapper.find('i'); - expect(icon.prop('className')).toBe('fa fa-filter'); - expect(icon.prop('title')).toBe('Add filter'); - wrapper.unmount(); - }); - - test('clicked', () => { - const testdata = { code: 'test' }; - const testnode = { data: testdata }; - const onClickHandler = jest.fn(); - - const wrapper = mount(); - const icon = wrapper.find('i'); - expect(icon.prop('className')).toBe('fa fa-filter'); - wrapper.simulate('click'); - expect(onClickHandler).toBeCalledTimes(1); - expect(onClickHandler).toHaveBeenCalledWith(testdata); - - wrapper.unmount(); - }); -}); diff --git a/packages/components/src/internal/components/ontology/OntologyTreePanel.test.tsx b/packages/components/src/internal/components/ontology/OntologyTreePanel.test.tsx new file mode 100644 index 0000000000..73a0b324d2 --- /dev/null +++ b/packages/components/src/internal/components/ontology/OntologyTreePanel.test.tsx @@ -0,0 +1,72 @@ +import React from 'react'; +import { fireEvent, render } from '@testing-library/react'; + +import { FilterIcon, OntologyTreePanel } from './OntologyTreePanel'; +import { PathModel } from './models'; + +jest.mock('./actions', () => ({ + ...jest.requireActual('./actions'), + fetchChildPaths: jest.fn().mockResolvedValue({ children: [] }), + fetchParentPaths: jest.fn().mockResolvedValue([]), +})); + +jest.mock('../files/FileTree', () => ({ + DEFAULT_ROOT_PREFIX: '|root', + FileTree: () =>
, +})); + +const DEFAULT_PROPS = { + root: new PathModel({ label: 'test label' }), + onNodeSelection: jest.fn(), +}; + +describe('OntologyTreePanel', () => { + test('default props', () => { + const { container } = render(); + expect(container.querySelector('.mock-file-tree')).not.toBeNull(); + }); +}); + +const DEFAULT_FILTER_ICON_PROPS = { + node: undefined, + onClick: undefined, + filters: undefined, +}; + +describe('FilterIcon', () => { + test('default props', () => { + const { container } = render(); + const icon = container.querySelector('i'); + expect(icon.className).toBe('fa fa-filter'); + }); + + test('node selected', () => { + const testnode = { data: { code: 'test' } }; + const testFilters = new Map().set('test', new PathModel()); + const { container } = render(); + const icon = container.querySelector('i'); + expect(icon.className).toBe('fa fa-filter selected'); + expect(icon.title).toBe('Remove filter'); + }); + + test('node not selected', () => { + const testnode = { data: { code: 'test' } }; + const testFilters = new Map().set('nope', new PathModel()); + const { container } = render(); + const icon = container.querySelector('i'); + expect(icon.className).toBe('fa fa-filter'); + expect(icon.title).toBe('Add filter'); + }); + + test('clicked', () => { + const testdata = { code: 'test' }; + const testnode = { data: testdata }; + const onClickHandler = jest.fn(); + const { container } = render(); + const icon = container.querySelector('i'); + expect(icon.className).toBe('fa fa-filter'); + fireEvent.click(icon); + expect(onClickHandler).toHaveBeenCalledTimes(1); + expect(onClickHandler).toHaveBeenCalledWith(testdata); + }); +}); diff --git a/packages/components/src/internal/components/ontology/OntologyTreeSearchContainer.spec.tsx b/packages/components/src/internal/components/ontology/OntologyTreeSearchContainer.spec.tsx deleted file mode 100644 index eb33c250f7..0000000000 --- a/packages/components/src/internal/components/ontology/OntologyTreeSearchContainer.spec.tsx +++ /dev/null @@ -1,143 +0,0 @@ -import React from 'react'; -import { mount, ReactWrapper } from 'enzyme'; - -import { Alert } from '../base/Alert'; - -import { - getOntologySearchTerm, - OntologySearchResultsMenu, - OntologyTreeSearchContainer, -} from './OntologyTreeSearchContainer'; -import { ConceptModel, OntologyModel } from './models'; - -const TEST_ONTOLOGY = new OntologyModel({ - abbreviation: 't', - name: 'test name', - conceptCount: 100, - description: 'test desc', -}); - -const TEST_SEARCH_HITS = [ - new ConceptModel({ - code: 'a', - label: 'A', - description: 'Description for a', - }), - new ConceptModel({ - code: 'b', - label: 'B', - description: 'Description for b', - }), -]; - -describe('OntologyTreeSearchContainer', () => { - test('default props', async () => { - const wrapper = mount( - - ); - expect(wrapper.find('.concept-search-container')).toHaveLength(1); - expect(wrapper.find('input')).toHaveLength(1); - expect(wrapper.find('input').prop('placeholder')).toBe('Search t'); - expect(wrapper.find(OntologySearchResultsMenu)).toHaveLength(0); // No initial search term, so no result element expected - - wrapper.unmount(); - }); -}); - -const DEFAULT_PROPS = { - searchHits: undefined, - totalHits: undefined, - isFocused: true, - error: undefined, - onItemClick: jest.fn, -}; - -describe('OntologySearchResultsMenu', () => { - function validate(wrapper: ReactWrapper, showMenu = false, itemCount = 0, showFooter = false): void { - expect(wrapper.find('ul.result-menu')).toHaveLength(showMenu ? 1 : 0); - expect(wrapper.find(Alert)).toHaveLength(showMenu ? 1 : 0); - expect(wrapper.find('li')).toHaveLength(itemCount); - expect(wrapper.find('.result-footer')).toHaveLength(showFooter ? 1 : 0); - } - - test('showMenu', () => { - let wrapper = mount(); - validate(wrapper); - wrapper.unmount(); - - wrapper = mount(); - validate(wrapper); - wrapper.unmount(); - - wrapper = mount(); - validate(wrapper, true, 1); - wrapper.unmount(); - - wrapper = mount(); - validate(wrapper, true); - wrapper.unmount(); - }); - - test('error', () => { - const wrapper = mount(); - validate(wrapper, true); - expect(wrapper.find(Alert).text()).toBe('test error'); - wrapper.unmount(); - }); - - test('no search results found', () => { - const wrapper = mount(); - validate(wrapper, true, 1); - expect(wrapper.find('li').text()).toBe('No search results found.'); - wrapper.unmount(); - }); - - test('totalHits footer', () => { - const wrapper = mount(); - validate(wrapper, true, 1, true); - expect(wrapper.find('.result-footer').text()).toContain('2 results found.'); - wrapper.unmount(); - }); - - test('with searchHits, with descriptions', () => { - const wrapper = mount( - - ); - validate(wrapper, true, TEST_SEARCH_HITS.length); - expect(wrapper.find('.selectable-item')).toHaveLength(TEST_SEARCH_HITS.length); - expect(wrapper.find('.bold')).toHaveLength(TEST_SEARCH_HITS.length); - expect(wrapper.find('.bold').at(0).text()).toBe(TEST_SEARCH_HITS[0].label); - expect(wrapper.find('.bold').at(1).text()).toBe(TEST_SEARCH_HITS[1].label); - expect(wrapper.find('.col-xs-2').at(0).text()).toBe(TEST_SEARCH_HITS[0].code); - expect(wrapper.find('.col-xs-2').at(1).text()).toBe(TEST_SEARCH_HITS[1].code); - expect(wrapper.find('.col-xs-10')).toHaveLength(0); - expect(wrapper.find('.col-xs-5')).toHaveLength(TEST_SEARCH_HITS.length * 2); - expect(wrapper.find('.col-xs-5').last().text()).toBe(TEST_SEARCH_HITS[1].description); - wrapper.unmount(); - }); - - test('with searchHits, without descriptions', () => { - const searchHits = [new ConceptModel({ code: 'a', label: 'A' })]; - - const wrapper = mount( - - ); - validate(wrapper, true, searchHits.length); - expect(wrapper.find('.col-xs-5')).toHaveLength(0); - expect(wrapper.find('.col-xs-10')).toHaveLength(searchHits.length); - expect(wrapper.find('.col-xs-10').text()).toBe(searchHits[0].label); - wrapper.unmount(); - }); -}); - -describe('getOntologySearchTerm', () => { - test('validate', () => { - const ont = new OntologyModel({ abbreviation: 'abbr' }); - expect(getOntologySearchTerm(ont, 'test')).toBe('+ontology:abbr AND ("test" OR test)'); - expect(getOntologySearchTerm(ont, 'test 123')).toBe('+ontology:abbr AND ("test 123" OR test 123)'); - }); -}); diff --git a/packages/components/src/internal/components/ontology/OntologyTreeSearchContainer.test.tsx b/packages/components/src/internal/components/ontology/OntologyTreeSearchContainer.test.tsx new file mode 100644 index 0000000000..a3de075863 --- /dev/null +++ b/packages/components/src/internal/components/ontology/OntologyTreeSearchContainer.test.tsx @@ -0,0 +1,124 @@ +import React from 'react'; +import { render } from '@testing-library/react'; + +import { + getOntologySearchTerm, + OntologySearchResultsMenu, + OntologyTreeSearchContainer, +} from './OntologyTreeSearchContainer'; +import { ConceptModel, OntologyModel } from './models'; + +const TEST_ONTOLOGY = new OntologyModel({ + abbreviation: 't', + name: 'test name', + conceptCount: 100, + description: 'test desc', +}); + +const TEST_SEARCH_HITS = [ + new ConceptModel({ code: 'a', label: 'A', description: 'Description for a' }), + new ConceptModel({ code: 'b', label: 'B', description: 'Description for b' }), +]; + +describe('OntologyTreeSearchContainer', () => { + test('default props', () => { + const { container } = render( + + ); + expect(container.querySelector('.concept-search-container')).not.toBeNull(); + expect(container.querySelector('input')).not.toBeNull(); + expect((container.querySelector('input') as HTMLInputElement).placeholder).toBe('Search t'); + expect(container.querySelector('ul.result-menu')).toBeNull(); + }); +}); + +const DEFAULT_PROPS = { + searchHits: undefined, + totalHits: undefined, + isFocused: true, + error: undefined, + onItemClick: jest.fn(), +}; + +describe('OntologySearchResultsMenu', () => { + test('showMenu', () => { + const { container, rerender } = render(); + expect(container.querySelector('ul.result-menu')).toBeNull(); + expect(container.querySelectorAll('li')).toHaveLength(0); + + rerender(); + expect(container.querySelector('ul.result-menu')).toBeNull(); + + rerender(); + expect(container.querySelector('ul.result-menu')).not.toBeNull(); + expect(container.querySelectorAll('li')).toHaveLength(1); + + rerender(); + expect(container.querySelector('ul.result-menu')).not.toBeNull(); + expect(container.querySelectorAll('li')).toHaveLength(0); + }); + + test('error', () => { + const { container } = render(); + expect(container.querySelector('ul.result-menu')).not.toBeNull(); + expect(container.querySelector('[role="alert"]')).not.toBeNull(); + expect(container.querySelector('[role="alert"]').textContent).toBe('test error'); + }); + + test('no search results found', () => { + const { container } = render(); + expect(container.querySelector('ul.result-menu')).not.toBeNull(); + expect(container.querySelectorAll('li')).toHaveLength(1); + expect(container.querySelector('li').textContent).toBe('No search results found.'); + }); + + test('totalHits footer', () => { + const { container } = render(); + expect(container.querySelector('ul.result-menu')).not.toBeNull(); + expect(container.querySelector('.result-footer')).not.toBeNull(); + expect(container.querySelector('.result-footer').textContent).toContain('2 results found.'); + }); + + test('with searchHits, with descriptions', () => { + const { container } = render( + + ); + expect(container.querySelector('ul.result-menu')).not.toBeNull(); + expect(container.querySelectorAll('li')).toHaveLength(TEST_SEARCH_HITS.length); + expect(container.querySelectorAll('.selectable-item')).toHaveLength(TEST_SEARCH_HITS.length); + const boldItems = container.querySelectorAll('.bold'); + expect(boldItems).toHaveLength(TEST_SEARCH_HITS.length); + expect(boldItems[0].textContent).toBe(TEST_SEARCH_HITS[0].label); + expect(boldItems[1].textContent).toBe(TEST_SEARCH_HITS[1].label); + const codeItems = container.querySelectorAll('.col-xs-2'); + expect(codeItems[0].textContent).toBe(TEST_SEARCH_HITS[0].code); + expect(codeItems[1].textContent).toBe(TEST_SEARCH_HITS[1].code); + expect(container.querySelectorAll('.col-xs-10')).toHaveLength(0); + const colXs5Items = container.querySelectorAll('.col-xs-5'); + expect(colXs5Items).toHaveLength(TEST_SEARCH_HITS.length * 2); + expect(colXs5Items[colXs5Items.length - 1].textContent).toBe(TEST_SEARCH_HITS[1].description); + }); + + test('with searchHits, without descriptions', () => { + const searchHits = [new ConceptModel({ code: 'a', label: 'A' })]; + const { container } = render( + + ); + expect(container.querySelectorAll('li')).toHaveLength(searchHits.length); + expect(container.querySelectorAll('.col-xs-5')).toHaveLength(0); + expect(container.querySelectorAll('.col-xs-10')).toHaveLength(searchHits.length); + expect(container.querySelector('.col-xs-10').textContent).toBe(searchHits[0].label); + }); +}); + +describe('getOntologySearchTerm', () => { + test('validate', () => { + const ont = new OntologyModel({ abbreviation: 'abbr' }); + expect(getOntologySearchTerm(ont, 'test')).toBe('+ontology:abbr AND ("test" OR test)'); + expect(getOntologySearchTerm(ont, 'test 123')).toBe('+ontology:abbr AND ("test 123" OR test 123)'); + }); +}); diff --git a/packages/components/src/internal/components/user/UserDeleteConfirmModal.tsx b/packages/components/src/internal/components/user/UserDeleteConfirmModal.tsx index 99d6b7a013..c2087b4773 100644 --- a/packages/components/src/internal/components/user/UserDeleteConfirmModal.tsx +++ b/packages/components/src/internal/components/user/UserDeleteConfirmModal.tsx @@ -63,12 +63,12 @@ export class UserDeleteConfirmModal extends React.Component {

Deletion of a user is permanent and cannot be undone. Deleted users: -

    -
  • will no longer be displayed with actions taken or data uploaded by them
  • -
  • will be removed from groups and permissions settings
  • -
  • cannot be reactivated
  • -

+
    +
  • will no longer be displayed with actions taken or data uploaded by them
  • +
  • will be removed from groups and permissions settings
  • +
  • cannot be reactivated
  • +

{Utils.pluralBasic(userCount, 'user')} will be deleted. Do you want to proceed?

{error && {error}}