diff --git a/app/classifier/tasks/components/TaskInputField/TaskInputField.jsx b/app/classifier/tasks/components/TaskInputField/TaskInputField.jsx
index 1e60385854..d0963c58ec 100644
--- a/app/classifier/tasks/components/TaskInputField/TaskInputField.jsx
+++ b/app/classifier/tasks/components/TaskInputField/TaskInputField.jsx
@@ -9,185 +9,144 @@ import { pxToRem, zooTheme } from '../../../../theme';
import TaskInputLabel from './components/TaskInputLabel';
import { doesTheLabelHaveAnImage } from './helpers';
-export const StyledTaskInputField = styled.label`
- align-items: baseline;
- background-color: ${theme('mode', {
+const DEFAULT = {
+ backgroundColor: theme('mode', {
dark: zooTheme.colors.darkTheme.background.default,
light: zooTheme.colors.lightTheme.background.default
- })};
- border: ${theme('mode', {
+ }),
+ border: theme('mode', {
dark: `2px solid ${zooTheme.colors.darkTheme.font}`,
light: '2px solid transparent'
- })};
- box-shadow: 1px 1px 2px 0 rgba(0,0,0,0.5);
- color: ${theme('mode', {
+ }),
+ color: theme('mode', {
dark: zooTheme.colors.darkTheme.font,
light: zooTheme.colors.lightTheme.font
- })};
+ })
+};
+
+const HOVER = {
+ gradientTop: theme('mode', {
+ dark: zooTheme.colors.darkTheme.button.answer.gradient.top,
+ light: zooTheme.colors.lightTheme.button.answer.gradient.top
+ }),
+ gradientBottom: theme('mode', {
+ dark: zooTheme.colors.darkTheme.button.answer.gradient.bottom,
+ light: zooTheme.colors.lightTheme.button.answer.gradient.bottom
+ }),
+ color: theme('mode', {
+ dark: zooTheme.colors.darkTheme.font,
+ light: 'black'
+ })
+};
+
+const CHECKED = {
+ background: theme('mode', {
+ dark: zooTheme.colors.teal.mid,
+ light: zooTheme.colors.teal.mid
+ }),
+ border: theme('mode', {
+ dark: `2px solid ${zooTheme.colors.teal.mid}`,
+ light: '2px solid transparent'
+ }),
+ color: theme('mode', {
+ dark: zooTheme.colors.darkTheme.font,
+ light: 'white'
+ })
+};
+
+export const StyledTaskLabel = styled.span`
+ align-items: baseline;
+ background-color: ${DEFAULT.backgroundColor};
+ border: ${DEFAULT.border};
+ box-shadow: 1px 1px 2px 0 rgba(0,0,0,0.5);
+ color: ${DEFAULT.color};
cursor: pointer;
display: flex;
margin: ${pxToRem(10)} 0;
- padding: ${(props) => { return doesTheLabelHaveAnImage(props.label) ? '0' : '1ch 2ch'; }};
+ padding: ${props => (doesTheLabelHaveAnImage(props.label) ? '0' : '1ch 2ch')};
+
+ &:hover {
+ background: linear-gradient(${HOVER.gradientTop}, ${HOVER.gradientBottom});
+ border-width: 2px;
+ border-style: solid;
+ border-left-color: transparent;
+ border-right-color: transparent;
+ border-top-color: ${HOVER.gradientTop};
+ border-bottom-color: ${HOVER.gradientBottom};
+ color: ${HOVER.color};
+ }
+`;
+
+export const StyledTaskInputField = styled.label`
position: relative;
- &:hover, &:focus, &[data-focus=true] {
- background: ${theme('mode', {
- dark: `linear-gradient(
- ${zooTheme.colors.darkTheme.button.answer.gradient.top},
- ${zooTheme.colors.darkTheme.button.answer.gradient.bottom}
- )`,
- light: `linear-gradient(
- ${zooTheme.colors.lightTheme.button.answer.gradient.top},
- ${zooTheme.colors.lightTheme.button.answer.gradient.bottom}
- )`
- })};
+ input {
+ opacity: 0.01;
+ position: absolute;
+ }
+
+ input:focus + ${StyledTaskLabel} {
+ background: linear-gradient(${HOVER.gradientTop}, ${HOVER.gradientBottom});
border-width: 2px;
border-style: solid;
border-left-color: transparent;
border-right-color: transparent;
- border-top-color: ${theme('mode', {
- dark: zooTheme.colors.darkTheme.button.answer.gradient.top,
- light: zooTheme.colors.lightTheme.button.answer.gradient.top
- })};
- border-bottom-color: ${theme('mode', {
- dark: zooTheme.colors.darkTheme.button.answer.gradient.bottom,
- light: zooTheme.colors.lightTheme.button.answer.gradient.bottom
- })};
- color: ${theme('mode', {
- dark: zooTheme.colors.darkTheme.font,
- light: 'black'
- })};
+ border-top-color: ${HOVER.gradientTop};
+ border-bottom-color: ${HOVER.gradientBottom};
+ color: ${HOVER.color};
}
- &:active {
- background: ${theme('mode', {
- dark: `linear-gradient(
- ${zooTheme.colors.darkTheme.button.answer.gradient.top},
- ${zooTheme.colors.darkTheme.button.answer.gradient.bottom}
- )`,
- light: `linear-gradient(
- ${zooTheme.colors.lightTheme.button.answer.gradient.top},
- ${zooTheme.colors.lightTheme.button.answer.gradient.bottom}
- )`
- })};
+ input:active + ${StyledTaskLabel} {
+ background: linear-gradient(${HOVER.gradientTop}, ${HOVER.gradientBottom});
border-width: 2px;
border-style: solid;
border-color: ${theme('mode', {
dark: zooTheme.colors.teal.dark,
light: zooTheme.colors.teal.mid
})};
- color: ${theme('mode', {
- dark: zooTheme.colors.darkTheme.font,
- light: 'black'
- })};
+ color: ${HOVER.color};
}
- &.active {
- background: ${theme('mode', {
- dark: zooTheme.colors.teal.mid,
- light: zooTheme.colors.teal.mid
- })};
- border: ${theme('mode', {
- dark: `2px solid ${zooTheme.colors.teal.mid}`,
- light: '2px solid transparent'
- })};
- color: ${theme('mode', {
- dark: zooTheme.colors.darkTheme.font,
- light: 'white'
- })}
+ input:checked + ${StyledTaskLabel} {
+ background: ${CHECKED.background};
+ border: ${CHECKED.border};
+ color: ${CHECKED.color}
}
- &.active:hover, &.active:focus, &.active[data-focus=true] {
- background: ${theme('mode', {
- dark: zooTheme.colors.teal.mid,
- light: zooTheme.colors.teal.mid
- })};
+ input:focus:checked + ${StyledTaskLabel},
+ input:checked + ${StyledTaskLabel}:hover {
border: ${theme('mode', {
dark: `2px solid ${zooTheme.colors.teal.dark}`,
light: `2px solid ${zooTheme.colors.teal.dark}`
})};
}
-
- input {
- opacity: 0.01;
- position: absolute;
- }
`;
-function shouldInputBeChecked(annotation, index, type) {
- if (type === 'radio') {
- const toolIndex = annotation._toolIndex || 0;
- if (toolIndex) {
- return index === toolIndex;
- }
- return index === annotation.value;
- }
-
- if (type === 'checkbox') {
- return (annotation.value && annotation.value.length > 0) ? annotation.value.includes(index) : false;
- }
-
- return false;
-}
-
-function shouldInputBeAutoFocused(annotation, index, name, type) {
- if (type === 'radio' && name === 'drawing-tool') {
- return index === 0;
- }
-
- return index === annotation.value;
-}
-
-export class TaskInputField extends React.Component {
- constructor() {
- super();
- this.unFocus = this.unFocus.bind(this);
- }
-
- onChange(e) {
- this.unFocus();
- this.props.onChange(e);
- }
-
- onFocus() {
- if (this.field) this.field.dataset.focus = true;
- }
-
- onBlur() {
- this.unFocus();
- }
-
- unFocus() {
- if (this.field) this.field.dataset.focus = false;
- }
-
- render() {
- return (
-
- { this.field = node; }}
- className={this.props.className}
- label={this.props.label}
- data-focus={false}
- >
-
-
-
-
- );
- }
+export function TaskInputField(props) {
+ return (
+
+
+
+
+
+
+
+
+ );
}
TaskInputField.defaultProps = {
+ autoFocus: false,
+ checked: false,
className: '',
label: '',
labelIcon: null,
@@ -198,21 +157,13 @@ TaskInputField.defaultProps = {
};
TaskInputField.propTypes = {
- annotation: PropTypes.shape({
- _key: PropTypes.number,
- task: PropTypes.string,
- value: PropTypes.oneOfType([
- PropTypes.arrayOf(PropTypes.number), // mulitple choice
- PropTypes.number, // single choice
- PropTypes.arrayOf(PropTypes.object), // drawing task
- PropTypes.object // null
- ])
- }).isRequired,
+ autoFocus: PropTypes.bool,
+ checked: PropTypes.bool,
className: PropTypes.string,
index: PropTypes.number.isRequired,
label: PropTypes.string,
labelIcon: PropTypes.oneOfType([PropTypes.node, PropTypes.object]),
- labelStatus: PropTypes.oneOfType([PropTypes.node, PropTypes.object]),
+ labelStatus: PropTypes.oneOfType([PropTypes.node, PropTypes.object]),
name: PropTypes.string,
onChange: PropTypes.func,
theme: PropTypes.string,
@@ -223,4 +174,4 @@ const mapStateToProps = state => ({
theme: state.userInterface.theme
});
-export default connect(mapStateToProps)(TaskInputField);
+export default connect(mapStateToProps)(React.memo(TaskInputField));
diff --git a/app/classifier/tasks/components/TaskInputField/TaskInputField.spec.jsx b/app/classifier/tasks/components/TaskInputField/TaskInputField.spec.jsx
index b145265a0f..59346a898e 100644
--- a/app/classifier/tasks/components/TaskInputField/TaskInputField.spec.jsx
+++ b/app/classifier/tasks/components/TaskInputField/TaskInputField.spec.jsx
@@ -12,8 +12,8 @@ import sinon from 'sinon';
import { TaskInputField, StyledTaskInputField } from './TaskInputField';
import { mockReduxStore, radioTypeAnnotation } from '../../testHelpers';
-describe('TaskInputField', function() {
- describe('render', function() {
+describe('TaskInputField', function () {
+ describe('render', function () {
let wrapper;
before(function () {
wrapper = mount(, mockReduxStore);
@@ -39,150 +39,24 @@ describe('TaskInputField', function() {
wrapper.setProps({ className: 'active' });
expect(wrapper.props().className).to.include('active');
});
- })
+ });
- describe('onChange method', function() {
- let onChangeSpy;
- let unFocusSpy;
+ describe('onChange method', function () {
let onChangePropsSpy;
let wrapper;
- before(function() {
- onChangeSpy = sinon.spy(TaskInputField.prototype, 'onChange');
- unFocusSpy = sinon.spy(TaskInputField.prototype, 'unFocus');
+ before(function () {
onChangePropsSpy = sinon.spy();
wrapper = mount(, mockReduxStore);
});
- afterEach(function() {
- onChangeSpy.resetHistory();
- unFocusSpy.resetHistory();
+ afterEach(function () {
onChangePropsSpy.resetHistory();
});
- after(function() {
- onChangeSpy.restore();
- unFocusSpy.restore();
- });
-
- it('should call onChange when the on change event is fired', function() {
- wrapper.find('input').simulate('change');
- expect(onChangeSpy.calledOnce).to.be.true;
- });
-
- it('should call onFocus in the onChange method', function() {
- wrapper.find('input').simulate('change');
- expect(unFocusSpy.calledOnce).to.be.true;
- });
-
- it('should call props.onChange in the onChange method', function() {
+ it('should call onChange when the on change event is fired', function () {
wrapper.find('input').simulate('change');
- expect(onChangePropsSpy.calledOnce).to.be.true;
- });
- });
-
- describe('onFocus method', function() {
- let wrapper;
- let onFocusSpy;
- before(function() {
- onFocusSpy = sinon.spy(TaskInputField.prototype, 'onFocus');
- wrapper = mount(, mockReduxStore);
- });
-
- afterEach(function() {
- onFocusSpy.resetHistory();
- });
-
- after(function() {
- onFocusSpy.restore();
- });
-
- it('should call onFocus when the on focus event fires', function() {
- wrapper.find('input').simulate('focus');
- expect(onFocusSpy.calledOnce).to.be.true;
- });
-
- // This isn't working. Can data attributes update in tests?
- // it('should set the data-focus attribute to true if this.field is defined', function() {
- // wrapper.find('input').simulate('focus');
- // wrapper.update();
- // expect(wrapper.instance().field).to.exist;
- // expect(wrapper.find(StyledTaskInputField).props()['data-focus']).to.be.true;
- // });
-
- // it('should not set the data-focus attribute to true if this.field is not defined', function() {
- // wrapper.instance().field = null;
- // wrapper.find('input').simulate('focus');
- // expect(wrapper.instance().field).to.not.exist;
- // expect(wrapper.find(StyledTaskInputField).props()['data-focus']).to.be.false;
- // });
- });
-
- describe('onBlur method', function() {
- let wrapper;
- let unFocusSpy;
- let onBlurSpy;
- before(function() {
- onBlurSpy = sinon.spy(TaskInputField.prototype, 'onBlur');
- unFocusSpy = sinon.spy(TaskInputField.prototype, 'unFocus');
- wrapper = mount(, mockReduxStore);
- });
-
- afterEach(function() {
- onBlurSpy.resetHistory();
- unFocusSpy.resetHistory();
- });
-
- afterEach(function() {
- onBlurSpy.restore();
- unFocusSpy.restore();
- });
-
- it('should call onBlur when the on blur event fires', function() {
- wrapper.find('input').simulate('blur');
- expect(onBlurSpy.calledOnce).to.be.true;
- });
-
- it('should call unFocus in the onBlur method', function() {
- wrapper.find('input').simulate('blur');
- expect(unFocusSpy.calledOnce).to.be.true;
+ expect(onChangePropsSpy).to.have.been.calledOnce;
});
});
-
- // describe.only('unFocus method', function() {
- // let wrapper;
- // let unFocusSpy;
- // before(function () {
- // unFocusSpy = sinon.spy(TaskInputField.prototype, 'unFocus');
- // wrapper = mount(, mockReduxStore);
- // });
-
- // afterEach(function () {
- // unFocusSpy.resetHistory();
- // });
-
- // afterEach(function () {
- // unFocusSpy.restore();
- // });
-
- // it('should set the data-focus attribute to true if this.field is defined', function() {
- // wrapper.instance().onFocus(); // set data-field focus to true
- // wrapper.update();
- // wrapper.instance().unFocus();
- // wrapper.update();
- // expect(wrapper.instance().field).to.exist;
- // expect(wrapper.find(StyledTaskInputField).props()['data-focus']).to.be.false;
- // });
-
- // it('should not set the data-focus attribute to true if this.field is not defined', function() {
- // wrapper.instance().onFocus(); // set data-field focus to true
- // wrapper.update();
- // console.log(wrapper.debug())
- // wrapper.instance().field = null;
- // expect(wrapper.instance().field).to.not.exist;
- // wrapper.instance().unFocus();
- // wrapper.update();
- // expect(wrapper.find(StyledTaskInputField).props()['data-focus']).to.be.true;
- // });
- // });
});
diff --git a/app/classifier/tasks/components/TaskInputField/index.js b/app/classifier/tasks/components/TaskInputField/index.js
index 31ae8368a8..a919cb3127 100644
--- a/app/classifier/tasks/components/TaskInputField/index.js
+++ b/app/classifier/tasks/components/TaskInputField/index.js
@@ -1,2 +1,2 @@
-export { default, StyledTaskInputField } from './TaskInputField';
+export { default, StyledTaskInputField, StyledTaskLabel } from './TaskInputField';
diff --git a/app/classifier/tasks/drawing/index.cjsx b/app/classifier/tasks/drawing/index.cjsx
index 362de8747e..26ab63210c 100644
--- a/app/classifier/tasks/drawing/index.cjsx
+++ b/app/classifier/tasks/drawing/index.cjsx
@@ -97,10 +97,13 @@ module.exports = createReactClass
tool._key ?= Math.random()
count = (true for mark in @props.annotation.value when mark.tool is i).length
translation = @props.translation.tools[i]
+ checked = i is (@props.annotation._toolIndex ? 0)
0) ? annotation.value.includes(i) : false;
answers.push(
);
+ summary = shallow();
});
it('should render without crashing', function () {
@@ -127,7 +130,7 @@ describe('MultipleChoiceSummary', function () {
});
it('should return "No answer" when an empty annotation is provided', function () {
- summary = mount();
+ summary = shallow();
const answer = summary.find('.answer');
expect(answer.text()).to.equal('No answer');
});
diff --git a/app/classifier/tasks/single/index.jsx b/app/classifier/tasks/single/index.jsx
index e76a359e2d..9e445134ba 100644
--- a/app/classifier/tasks/single/index.jsx
+++ b/app/classifier/tasks/single/index.jsx
@@ -30,11 +30,14 @@ export default class SingleChoiceTask extends React.Component {
if (!answer._key) {
answer._key = Math.random();
}
+ const checked = i === annotation.value;
answers.push(
, mockReduxStore);
+ wrapper = shallow(, mockReduxStore);
});
it('should render without crashing', function () {
@@ -24,12 +25,12 @@ describe('SingleChoiceTask', function () {
});
it('should have a question', function () {
- const question = wrapper.find('.question');
- expect(question.hostNodes()).to.have.lengthOf(1);
+ const question = wrapper.find(GenericTask).prop('question');
+ expect(question).to.equal(radioTypeTask.question);
});
it('should have answers', function () {
- expect(wrapper.find('TaskInputField')).to.have.lengthOf(radioTypeTask.answers.length);
+ expect(wrapper.find(GenericTask).prop('answers')).to.have.lengthOf(radioTypeTask.answers.length);
});
});
@@ -45,7 +46,7 @@ describe('SingleChoiceTask', function () {
});
beforeEach(function() {
- wrapper = mount(
+ wrapper = shallow(
);
+ summary = shallow();
});
it('should render without crashing', function () {
@@ -130,13 +132,13 @@ describe('SingleChoiceSummary', function () {
});
it('should have the correct answer label when the value if falsy (i.e. 0)', function () {
- summary = mount();
+ summary = shallow();
const answers = summary.find('.answer');
expect(answers.text()).to.not.equal('No answer');
});
it('should return "No answer" when annotation is null', function () {
- summary = mount();
+ summary = shallow();
const answers = summary.find('.answer');
expect(answers.text()).to.equal('No answer');
});