function () {
    const ctx = {}
    let component

    beforeEach(function () {
      component = this.subject({
        bunsenId: 'name',
        cellConfig: Ember.Object.create({}),
        model: {},
        onChange () {},
        store: Ember.Object.create({}),
        state: Ember.Object.create({})
      })
      ctx.component = component
    })

    validatePropTypes({
      bunsenId: PropTypes.string.isRequired,
      cellConfig: PropTypes.EmberObject.isRequired,
      errorMessage: PropTypes.oneOf([
        PropTypes.null,
        PropTypes.string
      ]),
      label: PropTypes.string,
      model: PropTypes.object.isRequired,
      onChange: PropTypes.func.isRequired,
      required: PropTypes.bool,
      store: PropTypes.EmberObject.isRequired,
      value: PropTypes.oneOf([
        PropTypes.array,
        PropTypes.bool,
        PropTypes.null,
        PropTypes.number,
        PropTypes.object,
        PropTypes.string
      ])
    })

    disabledTests(ctx)
    renderErrorMessageTests(ctx)
  }
  function () {
    const ctx = {}
    let component, sandbox

    beforeEach(function () {
      sandbox = sinon.sandbox.create()
      component = this.subject({
        bunsenId: 'foo',
        bunsenModel: {
          type: 'boolean'
        },
        bunsenStore: Ember.Object.create({}),
        cellConfig: Ember.Object.create({
          properties: {
            model: 'foo',
            type: 'button-group'
          }
        }),
        onChange () {},
        state: Ember.Object.create({})
      })
      ctx.component = component
    })

    afterEach(function () {
      sandbox.restore()
    })

    validatePropTypes({
      bunsenId: PropTypes.string.isRequired,
      bunsenModel: PropTypes.object.isRequired,
      bunsenStore: PropTypes.EmberObject.isRequired,
      cellConfig: PropTypes.EmberObject.isRequired,
      errorMessage: PropTypes.oneOfType([
        PropTypes.null,
        PropTypes.string
      ]),
      label: PropTypes.string,
      onChange: PropTypes.func.isRequired,
      required: PropTypes.bool,
      value: PropTypes.oneOfType([
        PropTypes.array,
        PropTypes.bool,
        PropTypes.null,
        PropTypes.number,
        PropTypes.object,
        PropTypes.string
      ])
    })

    disabledTests(ctx)
    renderErrorMessageTests(ctx)

    describe('options', function () {
      let validateValuesSpy

      beforeEach(function () {
        validateValuesSpy = sandbox.stub(helpers, 'validateValues')
      })

      describe('when type is boolean', function () {
        beforeEach(function () {
          component.set('bunsenModel.type', 'boolean')
        })

        it('returns expected options', function () {
          expect(component.get('options')).to.eql(['On', 'Off'])
        })
      })

      describe('when type is number', function () {
        let options, values

        beforeEach(function () {
          values = [0, 0.5, 1]
          component.set('bunsenModel.enum', values)
          component.set('bunsenModel.type', 'number')
          options = component.get('options')
        })

        it('validates values', function () {
          expect(validateValuesSpy.callCount).to.eql(1)
        })

        it('returns expected options', function () {
          expect(options).to.eql([0, 0.5, 1])
        })
      })

      describe('when type is string', function () {
        let options, values

        beforeEach(function () {
          values = ['one', 'two', 'three']
          component.set('bunsenModel.enum', values)
          component.set('bunsenModel.type', 'string')
          options = component.get('options')
        })

        it('validates values', function () {
          expect(validateValuesSpy.callCount).to.eql(1)
        })

        it('returns expected options', function () {
          expect(options).to.eql(['One', 'Two', 'Three'])
        })
      })
    })

    it('size defaults to medium', function () {
      expect(component.get('size')).to.eql('medium')
    })

    it('size can be overridden by properties.size', function () {
      component.set('cellConfig.properties', {size: 'small'})
      expect(component.get('size')).to.eql('small')
    })

    describe('.parseValue()', function () {
      describe('when type is boolean', function () {
        beforeEach(function () {
          component.set('bunsenModel.type', 'boolean')
        })

        it('returns true when selected index is 0', function () {
          expect(component.parseValue(0)).to.be.true
        })

        it('returns false when selected index is 1', function () {
          expect(component.parseValue(1)).to.be.false
        })
      })

      describe('when type is number', function () {
        let values

        beforeEach(function () {
          values = [0, 0.5, 1]
          component.set('bunsenModel.enum', values)
          component.set('bunsenModel.type', 'number')
        })

        it('returns expected value for selected index', function () {
          values.forEach((value, index) => {
            expect(component.parseValue(index)).to.eql(value)
          })
        })
      })

      describe('when type is string', function () {
        let values

        beforeEach(function () {
          values = ['one', 'two', 'three']
          component.set('bunsenModel.enum', values)
          component.set('bunsenModel.type', 'string')
        })

        it('returns expected value for selected index', function () {
          values.forEach((value, index) => {
            expect(component.parseValue(index)).to.eql(value)
          })
        })
      })
    })

    describe('when onChange property is omitted', function () {
      beforeEach(function () {
        component.set('onChange', undefined)
      })

      it('does not throw an error when onChange action is triggered', function () {
        expect(function () {
          component.get('actions.onChange').call(component, 0)
        }).not.to.throw(Error)
      })
    })
  }
  function () {
    const ctx = {}
    let component

    beforeEach(function () {
      component = this.subject({
        bunsenId: 'name',
        bunsenModel: {},
        bunsenStore: Ember.Object.create({}),
        cellConfig: Ember.Object.create({}),
        onChange () {},
        state: Ember.Object.create({})
      })
      ctx.component = component
    })

    validatePropTypes({
      bunsenId: PropTypes.string.isRequired,
      bunsenModel: PropTypes.object.isRequired,
      bunsenStore: PropTypes.EmberObject.isRequired,
      cellConfig: PropTypes.EmberObject.isRequired,
      errorMessage: PropTypes.oneOfType([
        PropTypes.null,
        PropTypes.string
      ]),
      label: PropTypes.string,
      onChange: PropTypes.func.isRequired,
      required: PropTypes.bool,
      value: PropTypes.oneOfType([
        PropTypes.array,
        PropTypes.bool,
        PropTypes.null,
        PropTypes.number,
        PropTypes.object,
        PropTypes.string
      ])
    })

    disabledTests(ctx)
    renderErrorMessageTests(ctx)

    /**
     * Helper that creates an attribute object populated with a given bunsenStore formValue
     * @param {Object} formValue - value of bunsenStore formValue
     * @returns {Object} attribute object
     */
    function createAttrs (formValue) {
      return {
        bunsenStore: {
          value: {
            formValue
          }
        }
      }
    }

    describe('hasQueryChanged', function () {
      let modelQuery, attrs, oldAttrs, component

      beforeEach(function () {
        modelQuery = {
          foo: '${bar}'
        }
        const formValue = {
          bar: 'baz'
        }
        const oldFormValue = {
          bar: 'bar'
        }

        attrs = createAttrs(formValue)
        oldAttrs = createAttrs(oldFormValue)

        component = this.subject({
          initialized: true,
          bunsenId: ''
        })
      })

      describe('when query is not defined', function () {
        it('returns true when queries are the same', function () {
          expect(component.hasQueryChanged(attrs, attrs, undefined)).to.be.ok
        })

        it('returns true when queries are not the same', function () {
          expect(component.hasQueryChanged(oldAttrs, attrs, undefined)).to.be.ok
        })
      })

      describe('when not initialized', function () {
        beforeEach(() => {
          component.set('initialized', false)
        })
        it('returns true when queries are the same', function () {
          expect(component.hasQueryChanged(attrs, attrs, undefined)).to.be.ok
        })

        it('returns true when queries are not the same', function () {
          expect(component.hasQueryChanged(oldAttrs, attrs, undefined)).to.be.ok
        })
      })

      describe('when queries initialized and query is defined', function () {
        it('returns false when queries are equal', function () {
          expect(component.hasQueryChanged(oldAttrs, oldAttrs, modelQuery)).to.not.be.ok
        })

        it('returns true when queries mismatch', function () {
          expect(component.hasQueryChanged(oldAttrs, attrs, modelQuery)).to.be.ok
        })
      })
    })
  }
  function () {
    const ctx = {}
    let component

    beforeEach(function () {
      component = this.subject({
        bunsenId: 'name',
        bunsenModel: {},
        bunsenStore: Ember.Object.create({}),
        cellConfig: Ember.Object.create({}),
        onChange () {},
        state: Ember.Object.create({})
      })
      ctx.component = component
    })

    validatePropTypes({
      bunsenId: PropTypes.string.isRequired,
      bunsenModel: PropTypes.object.isRequired,
      bunsenStore: PropTypes.EmberObject.isRequired,
      cellConfig: PropTypes.EmberObject.isRequired,
      errorMessage: PropTypes.oneOfType([
        PropTypes.null,
        PropTypes.string
      ]),
      label: PropTypes.string,
      onChange: PropTypes.func.isRequired,
      required: PropTypes.bool,
      value: PropTypes.oneOfType([
        PropTypes.array,
        PropTypes.bool,
        PropTypes.null,
        PropTypes.number,
        PropTypes.object,
        PropTypes.string
      ])
    })

    disabledTests(ctx)
    renderErrorMessageTests(ctx)

    it('onBlur action sets showErrorMessage to true', function () {
      component.set('showErrorMessage', true)
      component.get('actions.onBlur').call(component)
      expect(component.get('renderErrorMessage')).to.not.be.null
    })

    it('onFocus action sets showErrorMessage to false', function () {
      component.set('showErrorMessage', true)
      component.get('actions.onFocus').call(component)
      expect(component.get('showErrorMessage')).to.be.false
    })

    describe('when onChange property is omitted', function () {
      beforeEach(function () {
        component.set('onChange', undefined)
      })

      it('does not throw an error when onChange action is triggered', function () {
        expect(function () {
          const e = {
            target: 'John'
          }
          component.get('actions.onChange').call(component, e)
        }).not.to.throw(Error)
      })
    })
  }
  function () {
    const ctx = {}
    let component

    beforeEach(function () {
      component = this.subject({
        bunsenId: 'name',
        bunsenModel: {},
        bunsenStore: Ember.Object.create({}),
        cellConfig: Ember.Object.create({}),
        onChange () {},
        state: Ember.Object.create({})
      })
      ctx.component = component
    })

    validatePropTypes({
      bunsenId: PropTypes.string.isRequired,
      bunsenModel: PropTypes.object.isRequired,
      bunsenStore: PropTypes.EmberObject.isRequired,
      cellConfig: PropTypes.EmberObject.isRequired,
      errorMessage: PropTypes.oneOfType([
        PropTypes.null,
        PropTypes.string
      ]),
      label: PropTypes.string,
      onChange: PropTypes.func.isRequired,
      required: PropTypes.bool,
      value: PropTypes.oneOfType([
        PropTypes.array,
        PropTypes.bool,
        PropTypes.null,
        PropTypes.number,
        PropTypes.object,
        PropTypes.string
      ])
    })

    disabledTests(ctx)
    renderErrorMessageTests(ctx)

    it('onBlur action sets showErrorMessage to true', function () {
      component.set('showErrorMessage', true)
      component.get('actions.onBlur').call(component)
      expect(component.get('renderErrorMessage')).to.not.be.null
    })

    it('onFocus action sets showErrorMessage to false', function () {
      component.set('showErrorMessage', true)
      component.get('actions.onFocus').call(component)
      expect(component.get('showErrorMessage')).to.be.false
    })

    describe('when onChange property is omitted', function () {
      beforeEach(function () {
        component.set('onChange', undefined)
      })

      it('does not throw an error when onChange action is triggered', function () {
        expect(function () {
          const e = {
            value: '1'
          }
          component.get('actions.onChange').call(component, e)
        }).not.to.throw(Error)
      })
    })

    describe('parseValue', function () {
      [
        {in: 0, out: 0},
        {in: 0.5, out: 0.5},
        {in: 1, out: 1},
        {in: '0', out: 0},
        {in: '0.5', out: 0.5},
        {in: '1', out: 1},
        {in: '', out: null},
        {in: undefined, out: null},
        {in: null, out: null},
        {in: 'test', out: null}
      ].forEach((test) => {
        it(`expect to return ${test.out} when input is ${test.in} (${typeof test.in})`, function () {
          const result = component.parseValue(test.in)
          expect(result).to.equal(test.out)
        })
      })
    })

    describe('renderValue', function () {
      [
        {in: null, out: ''},
        {in: undefined, out: ''},
        {in: '', out: ''},
        {in: 'test', out: 'test'}
      ].forEach((test) => {
        it(`returns "${test.out}" when value is ${test.in} (${typeof test.in})`, function () {
          component.set('value', test.in)
          expect(component.get('renderValue')).to.equal(test.out)
        })
      })
    })
  }