describe('Observer', function()

  describe('create', function()
    local function expectObserverToBeInCleanStateAndValid(observer)
      expect(observer).to.be.an(Rx.Observer)
      expect(observer.stopped).to.equal(false)
    end

    it('works when no parameters is passed', function()
      local observer = Rx.Observer.create()
      expectObserverToBeInCleanStateAndValid(observer)
    end)

    it('works when onNext callback parameter is passed', function()
      local observer = Rx.Observer.create(function() end)
      expectObserverToBeInCleanStateAndValid(observer)
    end)

    it('works when onError callback parameter is passed', function()
      local observer = Rx.Observer.create(nil, function() end)
      expectObserverToBeInCleanStateAndValid(observer)
    end)

    it('works when onComplete callback parameter is passed', function()
      local observer = Rx.Observer.create(nil, nil, function() end)
      expectObserverToBeInCleanStateAndValid(observer)
    end)

    it('works when all callback parameters are passed', function()
      local observer = Rx.Observer.create(function() end, function() end, function() end)
      expectObserverToBeInCleanStateAndValid(observer)
    end)
  end)

  describe('onNext', function()
    it('calls custom onNext callback if it was provided', function()
      local onNext = spy()
      local observer = Rx.Observer.create(onNext, nil, nil)
      observer:onNext()
      expect(#onNext).to.equal(1)
    end)

    it('passes all arguments to custom onNext callback if it was provided', function()
      local onNext = spy()
      local observer = Rx.Observer.create(onNext, nil, nil)
      observer:onNext(1, '2', 3, nil, 5, { key = 6 })
      expect(onNext).to.equal({{1, '2', 3, nil, 5, { key = 6 }}})
    end)

    it('works and does not error when custom onNext callback was not provided', function()
      local observer = Rx.Observer.create()
      local errors = {}

      -- would gladly use something like to_not.fail() here but it's
      -- not quite good with producing useful error messages
      local success = tryCall(function() observer:onNext() end, errors)
      tryCall(function() expect(success).to.equal(true) end, errors)
      throwErrorsIfAny(errors)
    end)

    describe('does not call custom onError callback', function()
      it('if observer already received completion notification', function()
        local onNext = spy()
        local observer = Rx.Observer.create(onNext, nil, nil)
        observer:onCompleted()
        observer:onNext()
        expect(#onNext).to.equal(0)
      end)

      it('if observer already received error notification', function()
        local onNext = spy()
        local observer = Rx.Observer.create(onNext, nil, nil)
        observer:onCompleted()
        observer:onNext()
        expect(#onNext).to.equal(0)
      end)
    end)
  end)

  describe('onError', function()
    it('causes an error by default if custom onError callback was not provided', function()
      local observer = Rx.Observer.create()
      expect(function() observer:onError() end).to.fail()
    end)

    it('calls custom onError callback if it was provided', function()
      local onError = spy()
      local observer = Rx.Observer.create(nil, onError, nil)
      observer:onError()
      expect(#onError).to.equal(1)
    end)

    it('passes first value from error notification to custom onError callback', function()
      local onError = spy()
      local observer = Rx.Observer.create(nil, onError, nil)
      observer:onError("err msg", "excessive arg", 1)
      expect(onError).to.equal({{"err msg"}})
    end)

    it('marks observer as stopped', function()
      local observer = Rx.Observer.create(nil, function() end, nil)
      observer:onError()
      expect(observer.stopped).to.equal(true)
    end)

    describe('does not call custom onError callback', function()
      it('if observer already received completion notification', function()
        local onError = spy()
        local observer = Rx.Observer.create(nil, onError, nil)
        observer:onCompleted()
        observer:onError()
        expect(#onError).to.equal(0)
      end)

      it('if observer already received error notification', function()
        local spyEnabled = false
        local onError = spy()
        local observer = Rx.Observer.create(nil,  function() if spyEnabled then onError() end end, nil)
        observer:onError()
        spyEnabled = true
        observer:onError()
        expect(#onError).to.equal(0)
      end)
    end)
  end)

  describe('onCompleted', function()
    it('calls custom onCompleted callback if it was provided', function()
      local onCompleted = spy()
      local observer = Rx.Observer.create(nil, nil, onCompleted)
      observer:onCompleted()
      expect(#onCompleted).to.equal(1)
    end)

    it('calls custom onCompleted callback with no parameters', function()
      local onCompleted = spy()
      local observer = Rx.Observer.create(nil, nil, onCompleted)
      observer:onCompleted("excessive arg", 1)
      expect(onCompleted).to.equal({{}})
    end)

    it('marks observer as stopped', function()
      local observer = Rx.Observer.create(nil, function() end, nil)
      observer:onCompleted()
      expect(observer.stopped).to.equal(true)
    end)

    describe('does not call custom onCompleted callback', function()
      it('if observer already received completion notification', function()
        local spyEnabled = false
        local onCompleted = spy()
        local observer = Rx.Observer.create(nil,  function() if spyEnabled then onCompleted() end end, nil)
        observer:onCompleted()
        spyEnabled = true
        observer:onCompleted()
        expect(#onCompleted).to.equal(0)
      end)

      it('if observer already received error notification', function()
        local onCompleted = spy()
        local observer = Rx.Observer.create(nil, function() end, onCompleted)
        observer:onError()
        observer:onCompleted()
        expect(#onCompleted).to.equal(0)
      end)
    end)
  end)
end)
