diff --git a/packages/lib-classifier/src/components/Classifier/components/FieldGuide/FieldGuide.stories.js b/packages/lib-classifier/src/components/Classifier/components/FieldGuide/FieldGuide.stories.js index 0228913429..26a8b6f603 100644 --- a/packages/lib-classifier/src/components/Classifier/components/FieldGuide/FieldGuide.stories.js +++ b/packages/lib-classifier/src/components/Classifier/components/FieldGuide/FieldGuide.stories.js @@ -2,7 +2,7 @@ import React from 'react' import zooTheme from '@zooniverse/grommet-theme' import { Box, Grommet } from 'grommet' import { Provider } from 'mobx-react' -import FieldGuideContainer from './FieldGuideContainer' +import FieldGuideConnector from './FieldGuideConnector' import FieldGuideStore from '@store/FieldGuideStore' import { FieldGuideFactory, @@ -42,7 +42,7 @@ const mockStore = { export default { title: 'Help Resources/Field Guide', - component: FieldGuideContainer, + component: FieldGuideConnector, parameters: { viewport: { defaultViewport: 'responsive' @@ -63,7 +63,7 @@ function FieldGuideStoryContext (props) { themeMode={(props.darkMode) ? 'dark' : 'light'} > - + diff --git a/packages/lib-classifier/src/components/Classifier/components/FieldGuide/FieldGuideConnector.js b/packages/lib-classifier/src/components/Classifier/components/FieldGuide/FieldGuideConnector.js new file mode 100644 index 0000000000..82a3e995e4 --- /dev/null +++ b/packages/lib-classifier/src/components/Classifier/components/FieldGuide/FieldGuideConnector.js @@ -0,0 +1,50 @@ +import React from 'react' +import { MobXProviderContext, observer } from 'mobx-react' +import FieldGuideWrapper from './FieldGuideWrapper' + +function useStores() { + const stores = React.useContext(MobXProviderContext) + + const { + active: fieldGuide, + attachedMedia: icons, + activeItemIndex, + setActiveItemIndex, + setModalVisibility, + showModal + } = stores.classifierStore.fieldGuide + + return { + activeItemIndex, + fieldGuide, + icons, + setActiveItemIndex, + setModalVisibility, + showModal + } +} + +function FieldGuideConnector(props) { + const { + activeItemIndex, + fieldGuide, + icons, + setActiveItemIndex, + setModalVisibility, + showModal + } = useStores() + + return ( + + ) +} + +export default observer(FieldGuideConnector) \ No newline at end of file diff --git a/packages/lib-classifier/src/components/Classifier/components/FieldGuide/FieldGuideConnector.spec.js b/packages/lib-classifier/src/components/Classifier/components/FieldGuide/FieldGuideConnector.spec.js new file mode 100644 index 0000000000..b5edf29776 --- /dev/null +++ b/packages/lib-classifier/src/components/Classifier/components/FieldGuide/FieldGuideConnector.spec.js @@ -0,0 +1,70 @@ +import { shallow } from 'enzyme' +import React from 'react' +import sinon from 'sinon' +import FieldGuideConnector from './FieldGuideConnector' +import FieldGuideWrapper from './FieldGuideWrapper' + +const mockStoreSnapshot = { + classifierStore: { + fieldGuide: { + active: '1', + attachedMedia: { + 2: { + id: '2', + src: 'media.jpg' + } + }, + activeItemIndex: -1, + setActiveItemIndex: () => {}, + setModalVisibility: () => {}, + showModal: false + } + } +} + +describe('FieldGuideConnector', function () { + let wrapper, useContextMock, containerProps + before(function () { + useContextMock = sinon.stub(React, 'useContext').callsFake(() => mockStoreSnapshot) + wrapper = shallow( + + ) + containerProps = wrapper.find(FieldGuideWrapper).props() + }) + + after(function () { + useContextMock.restore() + }) + + it('should render without crashing', function () { + expect(wrapper).to.be.ok() + }) + + it('should pass the active field guide as a prop', function () { + expect(containerProps.fieldGuide).to.deep.equal(mockStoreSnapshot.classifierStore.fieldGuide.active) + }) + + it('should pass the attached media as the icons prop', function () { + expect(containerProps.icons).to.deep.equal(mockStoreSnapshot.classifierStore.fieldGuide.attachedMedia) + }) + + it('should pass the activeItemIndex as a prop', function () { + expect(containerProps.activeItemIndex).to.deep.equal(mockStoreSnapshot.classifierStore.fieldGuide.activeItemIndex) + }) + + it('should pass the setActiveItemIndex function', function () { + expect(containerProps.setActiveItemIndex).to.deep.equal(mockStoreSnapshot.classifierStore.fieldGuide.setActiveItemIndex) + }) + + it('should pass the setModalVisibility function', function () { + expect(containerProps.setModalVisibility).to.deep.equal(mockStoreSnapshot.classifierStore.fieldGuide.setModalVisibility) + }) + + it('should pass the showModal boolean as a prop', function () { + expect(containerProps.showModal).to.deep.equal(mockStoreSnapshot.classifierStore.fieldGuide.showModal) + }) + + it('should pass along any other props', function () { + expect(containerProps.foo).to.equal('bar') + }) +}) \ No newline at end of file diff --git a/packages/lib-classifier/src/components/Classifier/components/FieldGuide/FieldGuideContainer.js b/packages/lib-classifier/src/components/Classifier/components/FieldGuide/FieldGuideContainer.js deleted file mode 100644 index 47920e78cc..0000000000 --- a/packages/lib-classifier/src/components/Classifier/components/FieldGuide/FieldGuideContainer.js +++ /dev/null @@ -1,60 +0,0 @@ -import { Modal } from '@zooniverse/react-components' -import counterpart from 'counterpart' -import React from 'react' -import PropTypes from 'prop-types' -import { inject, observer } from 'mobx-react' -import FieldGuideButton from './components/FieldGuideButton' -import FieldGuide from './components/FieldGuide' -import en from './locales/en' - -counterpart.registerTranslations('en', en) - -function storeMapper (stores) { - const { setModalVisibility, showModal } = stores.classifierStore.fieldGuide - return { - setModalVisibility, - showModal - } -} - -@inject(storeMapper) -@observer -class FieldGuideContainer extends React.Component { - onClose () { - const { setModalVisibility } = this.props - setModalVisibility(false) - } - - render () { - const { - showModal - } = this.props - - return ( - <> - - - - - - ) - } -} - -FieldGuideContainer.wrappedComponent.defaultProps = { - showModal: false -} - -FieldGuideContainer.wrappedComponent.propTypes = { - setModalVisibility: PropTypes.func.isRequired, - showModal: PropTypes.bool -} - -export default FieldGuideContainer diff --git a/packages/lib-classifier/src/components/Classifier/components/FieldGuide/FieldGuideContainer.spec.js b/packages/lib-classifier/src/components/Classifier/components/FieldGuide/FieldGuideContainer.spec.js deleted file mode 100644 index 4f164ee5a3..0000000000 --- a/packages/lib-classifier/src/components/Classifier/components/FieldGuide/FieldGuideContainer.spec.js +++ /dev/null @@ -1,38 +0,0 @@ -import { shallow } from 'enzyme' -import React from 'react' -import sinon from 'sinon' -import { Modal } from '@zooniverse/react-components' -import FieldGuideContainer from './FieldGuideContainer' - -describe('Component > FieldGuideContainer', function () { - let wrapper - - before(function () { - wrapper = shallow( { }} showModal={false} />) - }) - - it('should render without crashing', function () { - expect(wrapper).to.be.ok() - }) - - it('should set the Modal active prop with the showModal prop', function () { - expect(wrapper.find(Modal).props().active).to.be.false() - wrapper.setProps({ showModal: true }) - expect(wrapper.find(Modal).props().active).to.be.true() - }) - - it('should not block interaction with the classifier', function () { - expect(wrapper.find(Modal).props().modal).to.be.false() - }) - - it('should be positioned on the right', function () { - expect(wrapper.find(Modal).props().position).to.equal('right') - }) - - it('should call setModalVisibility onClose of the modal', function () { - const setModalVisibilitySpy = sinon.spy() - const wrapper = shallow() - wrapper.instance().onClose() - expect(setModalVisibilitySpy).to.have.been.calledOnceWith(false) - }) -}) diff --git a/packages/lib-classifier/src/components/Classifier/components/FieldGuide/FieldGuideWrapper.js b/packages/lib-classifier/src/components/Classifier/components/FieldGuide/FieldGuideWrapper.js new file mode 100644 index 0000000000..70227c1cf2 --- /dev/null +++ b/packages/lib-classifier/src/components/Classifier/components/FieldGuide/FieldGuideWrapper.js @@ -0,0 +1,50 @@ +import React from 'react' +import PropTypes from 'prop-types' +import { observable } from 'mobx' +import { PropTypes as MobXPropTypes } from 'mobx-react' +import { ResponsiveContext } from 'grommet' +import FieldGuideButton from './components/FieldGuideButton' +import FieldGuide from './components/FieldGuide' + +function FieldGuideWrapper (props) { + const { + fieldGuide, + setModalVisibility, + showModal + } = props + + return ( + <> + setModalVisibility(true)} /> + {showModal && + + {size => ( + setModalVisibility(false)} + size={size} + {...props} + /> + )} + + } + + ) +} + +FieldGuideWrapper.defaultProps = { + activeItemIndex: -1, + fieldGuide: null, + icons: observable.map(), + showModal: false +} + +FieldGuideWrapper.propTypes = { + activeItemIndex: PropTypes.number, + fieldGuide: PropTypes.object, + icons: MobXPropTypes.observableMap, + setActiveItemIndex: PropTypes.func.isRequired, + setModalVisibility: PropTypes.func.isRequired, + showModal: PropTypes.bool +} + +export default FieldGuideWrapper diff --git a/packages/lib-classifier/src/components/Classifier/components/FieldGuide/FieldGuideWrapper.spec.js b/packages/lib-classifier/src/components/Classifier/components/FieldGuide/FieldGuideWrapper.spec.js new file mode 100644 index 0000000000..6439af3969 --- /dev/null +++ b/packages/lib-classifier/src/components/Classifier/components/FieldGuide/FieldGuideWrapper.spec.js @@ -0,0 +1,125 @@ +import { mount, shallow } from 'enzyme' +import React from 'react' +import sinon from 'sinon' +import { observable } from 'mobx' +import { Grommet } from 'grommet' +import { zooTheme } from '@zooniverse/grommet-theme' +import FieldGuideWrapper from './FieldGuideWrapper' +import FieldGuide from './components/FieldGuide' +import FieldGuideButton from './components/FieldGuideButton' +import { FieldGuideFactory } from '@test/factories' + +describe('Component > FieldGuideWrapper', function () { + const fieldGuide = FieldGuideFactory.build() + const icons = observable.map() + + it('should render without crashing', function () { + const wrapper = shallow( + { }} + setModalVisibility={() => { }} + showModal={false} + /> + ) + expect(wrapper).to.be.ok() + }) + + it('should not show the field guide', function () { + const wrapper = shallow( + { }} + setModalVisibility={() => { }} + showModal={false} + /> + ) + expect(wrapper.find(FieldGuide)).to.have.lengthOf(0) + }) + + describe('when the field guide is shown', function () { + let wrapper + let showModal = true + const setActiveItemIndexSpy = sinon.spy() + const setModalVisibilityStub = sinon.stub().callsFake((boolean) => { showModal = boolean }) + beforeEach(function () { + wrapper = mount( + , { + wrappingComponent: Grommet, + wrappingComponentProps: { theme: zooTheme } + } + ) + }) + + it('should display the FieldGuide based on the showModal prop', function () { + expect(wrapper.find(FieldGuide)).to.have.lengthOf(1) + }) + + it('should pass the expected props', function () { + const props = wrapper.find(FieldGuide).props() + expect(props.activeItemIndex).to.equal(-1) + expect(props.fieldGuide).to.equal(fieldGuide) + expect(props.icons).to.equal(icons) + expect(props.onClose).to.be.a('function') + expect(props.setActiveItemIndex).to.equal(setActiveItemIndexSpy) + }) + + it('should set the modal visibility to be false with onClose', function () { + expect(wrapper.find(FieldGuide)).to.have.lengthOf(1) + wrapper.find(FieldGuide).prop('onClose')() + expect(setModalVisibilityStub).to.have.been.calledOnceWith(false) + wrapper.setProps({ showModal }) + expect(wrapper.find(FieldGuide)).to.have.lengthOf(0) + }) + }) + + describe('FieldGuideButton', function () { + let wrapper + let showModal = false + const setModalVisibilityStub = sinon.stub().callsFake((boolean) => { showModal = boolean }) + beforeEach(function () { + wrapper = mount( + {}} + setModalVisibility={setModalVisibilityStub} + showModal={showModal} + />, { + wrappingComponent: Grommet, + wrappingComponentProps: { theme: zooTheme } + } + ) + }) + + it('should render a FieldGuideButton', function () { + expect(wrapper.find(FieldGuideButton)).to.have.lengthOf(1) + }) + + it('should pass the expected props', function () { + const props = wrapper.find(FieldGuideButton).props() + expect(props.fieldGuide).to.equal(fieldGuide) + expect(props.onOpen).to.be.a('function') + }) + + it('should set the modal visibility to be true with onOpen', function () { + expect(wrapper.find(FieldGuide)).to.have.lengthOf(0) + wrapper.find(FieldGuideButton).prop('onOpen')() + expect(setModalVisibilityStub).to.have.been.calledOnceWith(true) + wrapper.setProps({ showModal }) + expect(wrapper.find(FieldGuide)).to.have.lengthOf(1) + }) + }) +}) diff --git a/packages/lib-classifier/src/components/Classifier/components/FieldGuide/components/FieldGuide/FieldGuide.js b/packages/lib-classifier/src/components/Classifier/components/FieldGuide/components/FieldGuide/FieldGuide.js index f4faf8427d..b3e0b380b8 100644 --- a/packages/lib-classifier/src/components/Classifier/components/FieldGuide/components/FieldGuide/FieldGuide.js +++ b/packages/lib-classifier/src/components/Classifier/components/FieldGuide/components/FieldGuide/FieldGuide.js @@ -1,58 +1,68 @@ -import { Box, ResponsiveContext } from 'grommet' -import { inject, observer } from 'mobx-react' +import { Box } from 'grommet' import PropTypes from 'prop-types' import React from 'react' -import { withTheme } from 'styled-components' +import { observable } from 'mobx' +import { PropTypes as MobXPropTypes } from 'mobx-react' import FieldGuideItems from './components/FieldGuideItems' import FieldGuideItem from './components/FieldGuideItem' -function storeMapper (stores) { - const { active: fieldGuide, activeItemIndex } = stores.classifierStore.fieldGuide - return { +function FieldGuide (props) { + const { activeItemIndex, - items: fieldGuide.items - } + boxHeight, + boxWidth, + className, + fieldGuide, + icons, + modalComponent, + modalProps, + setActiveItemIndex, + } = props + const items = fieldGuide?.items || [] + const item = items[activeItemIndex] + const ModalComponent = modalComponent + return ( + + + {item + ? + : + } + + + ) } -@inject(storeMapper) -@withTheme -@observer -class FieldGuide extends React.Component { - render () { - const { activeItemIndex, className, items } = this.props - return ( - - {size => { - const height = (size === 'small') ? '100%' : '415px' - const width = (size === 'small') ? '100%' : '490px' - return ( - - {items[activeItemIndex] - ? - : - } - - ) - }} - - ) - } -} - -FieldGuide.wrappedComponent.defaultProps = { +FieldGuide.defaultProps = { activeItemIndex: -1, - className: '' + boxHeight: '415px', + boxWidth: '490px', + className: '', + icons: observable.map(), + onClose: () => {}, + size: 'large', + setActiveItemIndex: () => {} } -FieldGuide.wrappedComponent.propTypes = { +FieldGuide.propTypes = { activeItemIndex: PropTypes.number, + boxHeight: PropTypes.string, + boxWidth: PropTypes.string, className: PropTypes.string, - items: PropTypes.arrayOf(PropTypes.object).isRequired + fieldGuide: PropTypes.object.isRequired, + icons: MobXPropTypes.observableMap, + modalComponent: PropTypes.func.isRequired, + modalProps: PropTypes.object.isRequired, + onClose: PropTypes.func, + size: PropTypes.string, + setActiveItemIndex: PropTypes.func } export default FieldGuide diff --git a/packages/lib-classifier/src/components/Classifier/components/FieldGuide/components/FieldGuide/FieldGuide.spec.js b/packages/lib-classifier/src/components/Classifier/components/FieldGuide/components/FieldGuide/FieldGuide.spec.js index c4d4d972ab..f846679723 100644 --- a/packages/lib-classifier/src/components/Classifier/components/FieldGuide/components/FieldGuide/FieldGuide.spec.js +++ b/packages/lib-classifier/src/components/Classifier/components/FieldGuide/components/FieldGuide/FieldGuide.spec.js @@ -1,51 +1,116 @@ -import { shallow, mount } from 'enzyme' +import { shallow } from 'enzyme' import React from 'react' +import { MovableModal } from '@zooniverse/react-components' +import { observable } from 'mobx' import FieldGuide from './FieldGuide' import FieldGuideItem from './components/FieldGuideItem' import FieldGuideItems from './components/FieldGuideItems' -import { FieldGuideMediumFactory } from '@test/factories' +import { FieldGuideFactory, FieldGuideMediumFactory } from '@test/factories' const medium = FieldGuideMediumFactory.build() -const items = [ - { - title: 'Cat', - icon: medium.id, - content: 'lorem ipsum' - }, - { - title: 'Dog', - content: 'Foo bar' +const fieldGuide = FieldGuideFactory.build({ + items: [ + { + title: 'Cat', + icon: medium.id, + content: 'lorem ipsum' + }, + { + title: 'Dog', + content: 'Foo bar' + }, + { title: 'Iguana', content: 'hello' }, + { title: 'Koala', content: '' }, + { title: 'Dragon', content: 'Why is this here?' } + ] +}) +const icons = observable.map({ + [medium.id]: medium +}) + +const minHeight = 415 +const minWidth = 490 + +const modalProps = { + active: true, + closeFn: () => {}, + modal: false, + pad: 'medium', + position: 'right', + rndProps: { + minHeight, + minWidth, + onResize: () => {}, + position: { + height: minHeight, + x: 0 - (minWidth + 60), // width plus margins + y: 0 - (minHeight + 60) * 0.5 // centers vertically + } }, - { title: 'Iguana', content: 'hello' }, - { title: 'Koala', content: '' }, - { title: 'Dragon', content: 'Why is this here?' } -] + title: 'Field Guide' +} describe('Component > FieldGuide', function () { it('should render without crashing', function () { const wrapper = shallow( - ) expect(wrapper).to.be.ok() }) - xit('should render FieldGuideItems if there is not an active item', function () { - const wrapper = shallow( - ) - expect(wrapper.find(FieldGuideItems)).to.have.lengthOf(1) - expect(wrapper.find(FieldGuideItem)).to.have.lengthOf(0) + describe('when there is not an active item', function () { + let wrapper + before(function () { + wrapper = shallow( + {}} + />) + }) + + it('should render FieldGuideItems', function () { + expect(wrapper.find(FieldGuideItems)).to.have.lengthOf(1) + expect(wrapper.find(FieldGuideItem)).to.have.lengthOf(0) + }) + + it('should pass the expected props to FieldGuideItems', function () { + const props = wrapper.find(FieldGuideItems).props() + expect(props.items).to.equal(fieldGuide.items) + expect(props.icons).to.equal(icons) + expect(props.setActiveItemIndex).to.be.a('function') + }) }) - xit('should render FieldGuideItem if there is an active item', function () { - const wrapper = mount( - ) - expect(wrapper.find(FieldGuideItems)).to.have.lengthOf(0) - expect(wrapper.find(FieldGuideItem)).to.have.lengthOf(1) + describe('when there is an active item', function () { + let wrapper + before(function () { + wrapper = shallow( + { }} + />) + }) + + it('should render FieldGuideItem if there is an active item', function () { + expect(wrapper.find(FieldGuideItems)).to.have.lengthOf(0) + expect(wrapper.find(FieldGuideItem)).to.have.lengthOf(1) + }) + + it('should pass the expected props to FieldGuideItem', function () { + const props = wrapper.find(FieldGuideItem).props() + expect(props.item).to.equal(fieldGuide.items[0]) + expect(props.icons).to.equal(icons) + expect(props.setActiveItemIndex).to.be.a('function') + }) }) }) diff --git a/packages/lib-classifier/src/components/Classifier/components/FieldGuide/components/FieldGuide/FieldGuideContainer.js b/packages/lib-classifier/src/components/Classifier/components/FieldGuide/components/FieldGuide/FieldGuideContainer.js new file mode 100644 index 0000000000..7300052882 --- /dev/null +++ b/packages/lib-classifier/src/components/Classifier/components/FieldGuide/components/FieldGuide/FieldGuideContainer.js @@ -0,0 +1,74 @@ +import PropTypes from 'prop-types' +import React from 'react' +import { MovableModal, Modal } from '@zooniverse/react-components' +import counterpart from 'counterpart' +import en from './locales/en' +import FieldGuide from './FieldGuide' + +counterpart.registerTranslations('en', en) + +function FieldGuideContainer (props) { + const { + onClose, + size, + ...rest + } = props + const modalComponent = (size === 'small') ? Modal : MovableModal + const minHeight = 415 + const minWidth = 490 + + const [height, setHeight] = React.useState(minHeight) + + function onResize(e, direction, ref, delta, position) { + if (height !== 'auto' && modalComponent === MovableModal) setHeight('auto') + } + + const boxHeight = (size === 'small') ? '100%' : `${minHeight}px` + const boxWidth = (size === 'small') ? '100%' : `${minWidth}px` + + const modalProps = { + active: true, + closeFn: onClose, + modal: false, + pad: 'medium', + position: 'right', + title: counterpart('FieldGuide.title') + } + const rndProps = { + minHeight, + minWidth, + onResize, + position: { + height, + x: 0 - (minWidth + 60), // width plus margins + y: 0 - (minHeight + 60) * 0.5 // centers vertically + } + } + const modalPropsToUse = (size === 'small') ? modalProps : Object.assign({}, modalProps, { rndProps }) + return ( + + ) +} + +FieldGuideContainer.defaultProps = { + className: '', + onClose: () => { }, + setActiveItemIndex: () => { }, + size: 'large' +} + +FieldGuideContainer.propTypes = { + className: PropTypes.string, + fieldGuide: PropTypes.object.isRequired, + onClose: PropTypes.func, + setActiveItemIndex: PropTypes.func, + size: PropTypes.string +} + +export default FieldGuideContainer \ No newline at end of file diff --git a/packages/lib-classifier/src/components/Classifier/components/FieldGuide/components/FieldGuide/FieldGuideContainer.spec.js b/packages/lib-classifier/src/components/Classifier/components/FieldGuide/components/FieldGuide/FieldGuideContainer.spec.js new file mode 100644 index 0000000000..8b4feebdb7 --- /dev/null +++ b/packages/lib-classifier/src/components/Classifier/components/FieldGuide/components/FieldGuide/FieldGuideContainer.spec.js @@ -0,0 +1,103 @@ +import { shallow } from 'enzyme' +import React from 'react' +import { MovableModal, Modal } from '@zooniverse/react-components' +import en from './locales/en' +import FieldGuideContainer from './FieldGuideContainer' +import { FieldGuideFactory, FieldGuideMediumFactory } from '@test/factories' + +const medium = FieldGuideMediumFactory.build() +const fieldGuide = FieldGuideFactory.build({ + items: [ + { + title: 'Cat', + icon: medium.id, + content: 'lorem ipsum' + }, + { + title: 'Dog', + content: 'Foo bar' + }, + { title: 'Iguana', content: 'hello' }, + { title: 'Koala', content: '' }, + { title: 'Dragon', content: 'Why is this here?' } + ] +}) + +describe('Component > FieldGuideContainer', function () { + it('should render without crashing', function () { + const wrapper = shallow( + ) + expect(wrapper).to.be.ok() + }) + + describe('when the window size is not small', function () { + let wrapper + before(function () { + wrapper = shallow( + ) + }) + + it('should render a MovableModal', function () { + expect(wrapper.props().modalComponent).to.equal(MovableModal) + }) + + it('should pass along props for the MovableModal', function () { + expect(wrapper.props().modalProps.rndProps).to.exist() + }) + + it('should render at a default minimum size', function () { + const minHeight = 415 + const minWidth = 490 + const { modalProps } = wrapper.props() + expect(modalProps.rndProps.minHeight).to.equal(minHeight) + expect(modalProps.rndProps.minWidth).to.equal(minWidth) + }) + + it('should render a title', function () { + expect(wrapper.props().modalProps.title).to.equal(en.FieldGuide.title) + }) + + it('should set the boxHeight and boxWidth to their minimum sizes', function () { + expect(wrapper.props().boxHeight).to.equal('415px') + expect(wrapper.props().boxWidth).to.equal('490px') + }) + + it('should set the movable modal height to auto on resize', function () { + expect(wrapper.props().modalProps.rndProps.position.height).to.equal(415) + wrapper.props().modalProps.rndProps.onResize() + expect(wrapper.props().modalProps.rndProps.position.height).to.equal('auto') + }) + }) + + describe('when the window size is small', function () { + let wrapper + before(function () { + wrapper = shallow( + ) + }) + + it('should render a Modal', function () { + expect(wrapper.props().modalComponent).to.equal(Modal) + }) + + it('should render a title', function () { + expect(wrapper.props().modalProps.title).to.equal(en.FieldGuide.title) + }) + + it('should set the boxHeight and boxWidth to 100%', function () { + expect(wrapper.props().boxHeight).to.equal('100%') + expect(wrapper.props().boxWidth).to.equal('100%') + }) + + it('should not pass along props for the MovableModal', function () { + expect(wrapper.props().modalProps.rndProps).to.be.undefined() + }) + }) +}) diff --git a/packages/lib-classifier/src/components/Classifier/components/FieldGuide/components/FieldGuide/components/FieldGuideItem/FieldGuideItem.js b/packages/lib-classifier/src/components/Classifier/components/FieldGuide/components/FieldGuide/components/FieldGuideItem/FieldGuideItem.js index 41b4fb2595..5f087a6b43 100644 --- a/packages/lib-classifier/src/components/Classifier/components/FieldGuide/components/FieldGuide/components/FieldGuideItem/FieldGuideItem.js +++ b/packages/lib-classifier/src/components/Classifier/components/FieldGuide/components/FieldGuide/components/FieldGuideItem/FieldGuideItem.js @@ -1,14 +1,13 @@ -import { Markdownz, Media } from '@zooniverse/react-components' +import { Markdownz, Media, SpacedHeading } from '@zooniverse/react-components' import counterpart from 'counterpart' import { Button, Box, Paragraph } from 'grommet' import { FormPrevious } from 'grommet-icons' import { observable } from 'mobx' -import { inject, observer, PropTypes as MobXPropTypes } from 'mobx-react' +import { PropTypes as MobXPropTypes } from 'mobx-react' import PropTypes from 'prop-types' import React from 'react' import styled, { css, withTheme } from 'styled-components' -import SpacedHeading from './components/SpacedHeading' import FieldGuideItemIcon from '../FieldGuideItemIcon' import en from './locales/en' @@ -47,17 +46,13 @@ const markdownComponents = { p: (nodeProps) => {nodeProps.children} } -function storeMapper (stores) { - const { setActiveItemIndex, attachedMedia: icons } = stores.classifierStore.fieldGuide - return { - icons, - setActiveItemIndex - } -} - -class FieldGuideItem extends React.Component { - render () { - const { className, icons, item, setActiveItemIndex } = this.props +function FieldGuideItem (props) { + const { + className, + icons, + item, + setActiveItemIndex + } = props const icon = icons.get(item.icon) return ( @@ -94,7 +89,6 @@ class FieldGuideItem extends React.Component { ) - } } FieldGuideItem.defaultProps = { @@ -115,10 +109,5 @@ FieldGuideItem.propTypes = { theme: PropTypes.object } -@inject(storeMapper) -@withTheme -@observer -class DecoratedFieldGuideItem extends FieldGuideItem { } - -export default DecoratedFieldGuideItem +export default withTheme(FieldGuideItem) export { FieldGuideItem } diff --git a/packages/lib-classifier/src/components/Classifier/components/FieldGuide/components/FieldGuide/components/FieldGuideItem/components/SpacedHeading/SpacedHeading.js b/packages/lib-classifier/src/components/Classifier/components/FieldGuide/components/FieldGuide/components/FieldGuideItem/components/SpacedHeading/SpacedHeading.js deleted file mode 100644 index 6ffb5bed75..0000000000 --- a/packages/lib-classifier/src/components/Classifier/components/FieldGuide/components/FieldGuide/components/FieldGuideItem/components/SpacedHeading/SpacedHeading.js +++ /dev/null @@ -1,29 +0,0 @@ -import { SpacedText } from '@zooniverse/react-components' -import { Heading } from 'grommet' -import { node, string } from 'prop-types' -import React from 'react' -import styled from 'styled-components' - -const StyledHeading = styled(Heading)` - font-size: 100%; - line-height: 1; -` - -function SpacedHeading (props) { - const { children, className, level } = props - return ( - - - {children} - - - ) -} - -SpacedHeading.propTypes = { - children: node, - className: string, - level: string -} - -export default SpacedHeading diff --git a/packages/lib-classifier/src/components/Classifier/components/FieldGuide/components/FieldGuide/components/FieldGuideItem/components/SpacedHeading/SpacedHeading.spec.js b/packages/lib-classifier/src/components/Classifier/components/FieldGuide/components/FieldGuide/components/FieldGuideItem/components/SpacedHeading/SpacedHeading.spec.js deleted file mode 100644 index eaf4189182..0000000000 --- a/packages/lib-classifier/src/components/Classifier/components/FieldGuide/components/FieldGuide/components/FieldGuideItem/components/SpacedHeading/SpacedHeading.spec.js +++ /dev/null @@ -1,21 +0,0 @@ -import { shallow } from 'enzyme' -import React from 'react' - -import SpacedHeading from './SpacedHeading' - -let wrapper - -const MOCK_PROPS = { - children: 'Foobar', - level: '1' -} - -describe('Component > SpacedHeading', function () { - before(function () { - wrapper = shallow() - }) - - it('should render without crashing', function () { - expect(wrapper).to.be.ok() - }) -}) diff --git a/packages/lib-classifier/src/components/Classifier/components/FieldGuide/components/FieldGuide/components/FieldGuideItem/components/SpacedHeading/index.js b/packages/lib-classifier/src/components/Classifier/components/FieldGuide/components/FieldGuide/components/FieldGuideItem/components/SpacedHeading/index.js deleted file mode 100644 index 08e9acf01b..0000000000 --- a/packages/lib-classifier/src/components/Classifier/components/FieldGuide/components/FieldGuide/components/FieldGuideItem/components/SpacedHeading/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from './SpacedHeading' diff --git a/packages/lib-classifier/src/components/Classifier/components/FieldGuide/components/FieldGuide/components/FieldGuideItems/FieldGuideItemAnchor.js b/packages/lib-classifier/src/components/Classifier/components/FieldGuide/components/FieldGuide/components/FieldGuideItemAnchor/FieldGuideItemAnchor.js similarity index 69% rename from packages/lib-classifier/src/components/Classifier/components/FieldGuide/components/FieldGuide/components/FieldGuideItems/FieldGuideItemAnchor.js rename to packages/lib-classifier/src/components/Classifier/components/FieldGuide/components/FieldGuide/components/FieldGuideItemAnchor/FieldGuideItemAnchor.js index 372e8fedc2..82cf829399 100644 --- a/packages/lib-classifier/src/components/Classifier/components/FieldGuide/components/FieldGuide/components/FieldGuideItems/FieldGuideItemAnchor.js +++ b/packages/lib-classifier/src/components/Classifier/components/FieldGuide/components/FieldGuide/components/FieldGuideItemAnchor/FieldGuideItemAnchor.js @@ -3,7 +3,7 @@ import React from 'react' import { observable } from 'mobx' import { Markdownz } from '@zooniverse/react-components' import PropTypes from 'prop-types' -import { inject, observer, PropTypes as MobXPropTypes } from 'mobx-react' +import { PropTypes as MobXPropTypes } from 'mobx-react' import { withTheme } from 'styled-components' import FieldGuideItemIcon from '../FieldGuideItemIcon' import counterpart from 'counterpart' @@ -20,7 +20,7 @@ export function AnchorLabel ({ className, icons, item }) { direction='column' width='100px' > - + {item.title} @@ -28,25 +28,22 @@ export function AnchorLabel ({ className, icons, item }) { ) } -function storeMapper (stores) { - const { attachedMedia: icons, setActiveItemIndex } = stores.classifierStore.fieldGuide - return { +function FieldGuideItemAnchor (props) { + const { + className, icons, - setActiveItemIndex - } -} + item, + itemIndex, + setActiveItemIndex, + theme + } = props -@inject(storeMapper) -@observer -class FieldGuideItemAnchor extends React.Component { - onClick (event, itemIndex) { - const { setActiveItemIndex } = this.props + function onClick (event, itemIndex) { event.preventDefault() setActiveItemIndex(itemIndex) } - render () { - const { className, icons, item, itemIndex, theme } = this.props + const label = const anchorColor = (theme.dark) ? 'light-3' : 'dark-5' return ( @@ -56,13 +53,12 @@ class FieldGuideItemAnchor extends React.Component { color={anchorColor} href={`#field-guide-item-${itemIndex}`} label={label} - onClick={(event) => this.onClick(event, itemIndex)} + onClick={(event) => onClick(event, itemIndex)} /> ) - } } -FieldGuideItemAnchor.wrappedComponent.defaultProps = { +FieldGuideItemAnchor.defaultProps = { className: '', icons: observable.map(), theme: { @@ -70,7 +66,7 @@ FieldGuideItemAnchor.wrappedComponent.defaultProps = { } } -FieldGuideItemAnchor.wrappedComponent.propTypes = { +FieldGuideItemAnchor.propTypes = { className: PropTypes.string, icons: MobXPropTypes.observableMap, item: PropTypes.object.isRequired, diff --git a/packages/lib-classifier/src/components/Classifier/components/FieldGuide/components/FieldGuide/components/FieldGuideItems/FieldGuideItemAnchor.spec.js b/packages/lib-classifier/src/components/Classifier/components/FieldGuide/components/FieldGuide/components/FieldGuideItemAnchor/FieldGuideItemAnchor.spec.js similarity index 69% rename from packages/lib-classifier/src/components/Classifier/components/FieldGuide/components/FieldGuide/components/FieldGuideItems/FieldGuideItemAnchor.spec.js rename to packages/lib-classifier/src/components/Classifier/components/FieldGuide/components/FieldGuide/components/FieldGuideItemAnchor/FieldGuideItemAnchor.spec.js index 627a42d0b5..183107bb1e 100644 --- a/packages/lib-classifier/src/components/Classifier/components/FieldGuide/components/FieldGuide/components/FieldGuideItems/FieldGuideItemAnchor.spec.js +++ b/packages/lib-classifier/src/components/Classifier/components/FieldGuide/components/FieldGuide/components/FieldGuideItemAnchor/FieldGuideItemAnchor.spec.js @@ -1,18 +1,21 @@ -import { shallow } from 'enzyme' +import { mount, shallow } from 'enzyme' import sinon from 'sinon' import React from 'react' import { observable } from 'mobx' import { Markdownz } from '@zooniverse/react-components' +import { Anchor } from 'grommet' import FieldGuideItemAnchor, { AnchorLabel } from './FieldGuideItemAnchor' import FieldGuideItemIcon from '../FieldGuideItemIcon' import { FieldGuideMediumFactory } from '@test/factories' -const medium = FieldGuideMediumFactory.build() +const mediumOne = FieldGuideMediumFactory.build() +const mediumTwo = FieldGuideMediumFactory.build() const attachedMedia = observable.map() -attachedMedia.set(medium.id, medium) +attachedMedia.set(mediumOne.id, mediumOne) +attachedMedia.set(mediumTwo.id, mediumTwo) const item = { title: 'Cat', - icon: medium.id, + icon: mediumOne.id, content: 'lorem ipsum' } const itemIndex = 0 @@ -20,7 +23,7 @@ const itemIndex = 0 describe('Component > FieldGuideItemAnchor', function () { it('should render without crashing', function () { const wrapper = shallow( - FieldGuideItemAnchor', function () { }) it('should use AnchorLabel as the label', function () { - const wrapper = shallow( - { }} />) - expect(wrapper.props().label.type).to.equal(AnchorLabel) + expect(wrapper.find(Anchor).props().label.type).to.equal(AnchorLabel) }) it('should call setActiveItemIndex on click', function () { const setActiveItemIndexSpy = sinon.spy() - const wrapper = shallow( - ) - wrapper.simulate('click', { preventDefault: () => {} }) + wrapper.find(Anchor).simulate('click', { preventDefault: () => {} }) expect(setActiveItemIndexSpy).to.have.been.calledOnceWith(itemIndex) }) @@ -82,5 +85,16 @@ describe('Component > FieldGuideItemAnchor', function () { expect(wrapper.find(FieldGuideItemIcon)).to.have.lengthOf(1) }) + + it('should render the correct icon', function () { + const wrapper = shallow( + ) + const icon = wrapper.find(FieldGuideItemIcon) + expect(icon.props().alt).to.equal(item.title) + expect(icon.props().icon).to.deep.equal(mediumOne) + }) }) }) diff --git a/packages/lib-classifier/src/components/Classifier/components/FieldGuide/components/FieldGuide/components/FieldGuideItemAnchor/index.js b/packages/lib-classifier/src/components/Classifier/components/FieldGuide/components/FieldGuide/components/FieldGuideItemAnchor/index.js new file mode 100644 index 0000000000..03f3f93c85 --- /dev/null +++ b/packages/lib-classifier/src/components/Classifier/components/FieldGuide/components/FieldGuide/components/FieldGuideItemAnchor/index.js @@ -0,0 +1 @@ +export { default } from './FieldGuideItemAnchor' \ No newline at end of file diff --git a/packages/lib-classifier/src/components/Classifier/components/FieldGuide/components/FieldGuide/components/FieldGuideItems/locales/en.json b/packages/lib-classifier/src/components/Classifier/components/FieldGuide/components/FieldGuide/components/FieldGuideItemAnchor/locales/en.json similarity index 100% rename from packages/lib-classifier/src/components/Classifier/components/FieldGuide/components/FieldGuide/components/FieldGuideItems/locales/en.json rename to packages/lib-classifier/src/components/Classifier/components/FieldGuide/components/FieldGuide/components/FieldGuideItemAnchor/locales/en.json diff --git a/packages/lib-classifier/src/components/Classifier/components/FieldGuide/components/FieldGuide/components/FieldGuideItems/FieldGuideItems.js b/packages/lib-classifier/src/components/Classifier/components/FieldGuide/components/FieldGuide/components/FieldGuideItems/FieldGuideItems.js index bd0730aee7..23a7b431a9 100644 --- a/packages/lib-classifier/src/components/Classifier/components/FieldGuide/components/FieldGuide/components/FieldGuideItems/FieldGuideItems.js +++ b/packages/lib-classifier/src/components/Classifier/components/FieldGuide/components/FieldGuide/components/FieldGuideItems/FieldGuideItems.js @@ -1,9 +1,9 @@ import { Box, Grid } from 'grommet' import React from 'react' import PropTypes from 'prop-types' -import FieldGuideItemAnchor from './FieldGuideItemAnchor' +import FieldGuideItemAnchor from '../FieldGuideItemAnchor' -function FieldGuideItems ({ items }) { +function FieldGuideItems ({ icons, items, setActiveItemIndex }) { return ( - {items.map((item, index) => )} + {items.map((item, index) => ( + + ))} + ) diff --git a/packages/lib-classifier/src/components/Classifier/components/FieldGuide/components/FieldGuide/components/FieldGuideItems/FieldGuideItems.spec.js b/packages/lib-classifier/src/components/Classifier/components/FieldGuide/components/FieldGuide/components/FieldGuideItems/FieldGuideItems.spec.js index c80a2e85b7..6c09fca5f0 100644 --- a/packages/lib-classifier/src/components/Classifier/components/FieldGuide/components/FieldGuide/components/FieldGuideItems/FieldGuideItems.spec.js +++ b/packages/lib-classifier/src/components/Classifier/components/FieldGuide/components/FieldGuide/components/FieldGuideItems/FieldGuideItems.spec.js @@ -2,7 +2,7 @@ import { shallow } from 'enzyme' import React from 'react' import { Grid } from 'grommet' import FieldGuideItems from './FieldGuideItems' -import FieldGuideItemAnchor from './FieldGuideItemAnchor' +import FieldGuideItemAnchor from '../FieldGuideItemAnchor' import { FieldGuideMediumFactory } from '@test/factories' const medium = FieldGuideMediumFactory.build() diff --git a/packages/lib-classifier/src/components/Classifier/components/FieldGuide/components/FieldGuide/index.js b/packages/lib-classifier/src/components/Classifier/components/FieldGuide/components/FieldGuide/index.js index f6884201ba..259585fa9c 100644 --- a/packages/lib-classifier/src/components/Classifier/components/FieldGuide/components/FieldGuide/index.js +++ b/packages/lib-classifier/src/components/Classifier/components/FieldGuide/components/FieldGuide/index.js @@ -1 +1 @@ -export { default } from './FieldGuide' +export { default } from './FieldGuideContainer' diff --git a/packages/lib-classifier/src/components/Classifier/components/FieldGuide/locales/en.json b/packages/lib-classifier/src/components/Classifier/components/FieldGuide/components/FieldGuide/locales/en.json similarity index 100% rename from packages/lib-classifier/src/components/Classifier/components/FieldGuide/locales/en.json rename to packages/lib-classifier/src/components/Classifier/components/FieldGuide/components/FieldGuide/locales/en.json diff --git a/packages/lib-classifier/src/components/Classifier/components/FieldGuide/components/FieldGuideButton/FieldGuideButton.js b/packages/lib-classifier/src/components/Classifier/components/FieldGuide/components/FieldGuideButton/FieldGuideButton.js index b56339e10e..dd2187d3d5 100644 --- a/packages/lib-classifier/src/components/Classifier/components/FieldGuide/components/FieldGuideButton/FieldGuideButton.js +++ b/packages/lib-classifier/src/components/Classifier/components/FieldGuide/components/FieldGuideButton/FieldGuideButton.js @@ -5,14 +5,13 @@ import React from 'react' import PropTypes from 'prop-types' import styled, { css, withTheme } from 'styled-components' import { tint } from 'polished' -import { inject, observer } from 'mobx-react' import en from './locales/en' import HelpIcon from './HelpIcon' counterpart.registerTranslations('en', en) -const StyledButton = styled(Button)` +export const StyledButton = styled(Button)` ${props => props.theme && css` background: ${props.theme.global.colors.brand}; padding: 15px 10px; @@ -54,39 +53,24 @@ export function ButtonLabel () { ) } -function storeMapper (stores) { - const { active: fieldGuide, setModalVisibility } = stores.classifierStore.fieldGuide - return { +function FieldGuideButton (props) { + const { fieldGuide, - setModalVisibility - } -} - -@inject(storeMapper) -@observer -class FieldGuideButton extends React.Component { - onClick () { - const { setModalVisibility } = this.props - setModalVisibility(true) - } + onOpen, + theme + } = props + const disabled = !fieldGuide || fieldGuide.items.length === 0 - render () { - const { - fieldGuide, - theme - } = this.props - const disabled = !fieldGuide || fieldGuide.items.length === 0 + return ( + } + disabled={disabled} + onClick={onOpen} + plain + theme={theme} + /> + ) - return ( - } - disabled={disabled} - onClick={this.onClick.bind(this)} - plain - theme={theme} - /> - ) - } } FieldGuideButton.defaultProps = { @@ -98,10 +82,11 @@ FieldGuideButton.defaultProps = { } } -FieldGuideButton.wrappedComponent.propTypes = { +FieldGuideButton.propTypes = { fieldGuide: PropTypes.object, - theme: PropTypes.object, - setModalVisibility: PropTypes.func.isRequired + onOpen: PropTypes.func.isRequired, + theme: PropTypes.object } export default withTheme(FieldGuideButton) +export { FieldGuideButton } diff --git a/packages/lib-classifier/src/components/Classifier/components/FieldGuide/components/FieldGuideButton/FieldGuideButton.spec.js b/packages/lib-classifier/src/components/Classifier/components/FieldGuide/components/FieldGuideButton/FieldGuideButton.spec.js index 0ff65554e1..dc70a8b28c 100644 --- a/packages/lib-classifier/src/components/Classifier/components/FieldGuide/components/FieldGuideButton/FieldGuideButton.spec.js +++ b/packages/lib-classifier/src/components/Classifier/components/FieldGuide/components/FieldGuideButton/FieldGuideButton.spec.js @@ -1,7 +1,8 @@ -import { shallow } from 'enzyme' +import { mount, shallow } from 'enzyme' import sinon from 'sinon' import React from 'react' -import FieldGuideButton, { ButtonLabel } from './FieldGuideButton' +import zooTheme from '@zooniverse/grommet-theme' +import { FieldGuideButton, ButtonLabel, StyledButton } from './FieldGuideButton' import { FieldGuideFactory, FieldGuideMediumFactory } from '@test/factories' const medium = FieldGuideMediumFactory.build() @@ -25,59 +26,66 @@ const fieldGuideWithoutItems = FieldGuideFactory.build() describe('Component > FieldGuideButton', function () { it('should render without crashing', function () { const wrapper = shallow( - {}} + onOpen={() => {}} + theme={zooTheme} />) expect(wrapper).to.be.ok() }) it('should disable the button if there isn\'t a field guide', function () { const wrapper = shallow( - { }} + { }} + theme={zooTheme} />) + expect(wrapper.props().disabled).to.be.true() }) it('should disable the button if the field guide doesn\'t have items', function () { const wrapper = shallow( - { }} + onOpen={() => { }} + theme={zooTheme} />) expect(wrapper.props().disabled).to.be.true() }) it('should enable the button if the field guide has items', function () { const wrapper = shallow( - { }} + onOpen={() => { }} + theme={zooTheme} />) expect(wrapper.props().disabled).to.be.false() }) it('should render a ButtonLabel for the label', function () { const wrapper = shallow( - { }} + onOpen={() => { }} + theme={zooTheme} />) expect(wrapper.props().label.type).to.equal(ButtonLabel) }) - it('should call setModalVisibility on click', function () { - const setModalVisibilitySpy = sinon.spy() - const wrapper = shallow( - ) - wrapper.simulate('click') - expect(setModalVisibilitySpy).to.have.been.calledOnceWith(true) + wrapper.find(StyledButton).simulate('click') + expect(onOpenSpy).to.have.been.calledOnce() }) describe('Component > ButtonLabel', function () { diff --git a/packages/lib-classifier/src/components/Classifier/components/FieldGuide/index.js b/packages/lib-classifier/src/components/Classifier/components/FieldGuide/index.js index 259585fa9c..eafffb737d 100644 --- a/packages/lib-classifier/src/components/Classifier/components/FieldGuide/index.js +++ b/packages/lib-classifier/src/components/Classifier/components/FieldGuide/index.js @@ -1 +1 @@ -export { default } from './FieldGuideContainer' +export { default } from './FieldGuideConnector'