import { fromJS } from 'immutable'
import withStateCache from '../withStateCache'

describe('withStateCache', () => {
  const stub = sinon.stub()
  class Helper {
    constructor (stub) {
      this.stub = stub
      this.internalState = fromJS({x: {y: null}})
    }

    state () {
      return this.internalState
    }

    @withStateCache((obj) => [['x']])
    testMethod () {
      this.stub()
      const x = this.state().get('x')

      return 2 * x.get('y')
    }

    @withStateCache((obj) => [['a', 'b'], ['c']])
    testMethod2 () {
      this.stub()
      const x = this.state().getIn(['a', 'b']).value
      const y = this.state().getIn(['c']).value
      return x + y
    }

    @withStateCache((obj) => [['x', obj.id]])
    testMethod3 () {
      this.stub()
      const id = this.state().get('id')
      return this.state().getIn(['x', id, 'y'])
    }
  }

  let helper

  beforeEach(() => {
    helper = new Helper(stub)
  })

  afterEach(() => {
    stub.resetHistory()
  })

  context('caching by multiple keys', () => {
    const obj1 = {value: 3}
    const obj2 = {value: 4}
    const state = fromJS({a: {b: null}, c: null})
      .setIn(['a', 'b'], obj1)
      .setIn(['c'], obj2)

    it('calls through to the original method', () => {
      const obj1 = {value: 3}
      const obj2 = {value: 4}
      const state = fromJS({a: {b: null}, c: null})
        .setIn(['a', 'b'], obj1)
        .setIn(['c'], obj2)
      helper.internalState = state
      expect(helper.testMethod2()).to.eq(7)
      expect(stub.callCount).to.eq(1)
    })

    it('caches when all keys are the same', () => {
      helper.internalState = state
      expect(helper.testMethod2()).to.eq(7)
      expect(helper.testMethod2()).to.eq(7)
      expect(stub.callCount).to.eq(1)
    })

    it('does not cache when either keys are different', () => {
      helper.internalState = state
      expect(helper.testMethod2()).to.eq(7)
      const state2 = state.setIn(['a', 'b'], {value: 5})
      const state3 = state.setIn(['c'], {value: 10})
      helper.internalState = state2
      expect(helper.testMethod2()).to.eq(9)
      helper.internalState = state3
      expect(helper.testMethod2()).to.eq(13)
      expect(stub.callCount).to.eq(3)
    })
  })

  context('caching by one key', () => {
    it('calls the decorated method', () => {
      helper.internalState = fromJS({x: {y: 5}})
      expect(helper.testMethod()).to.eq(10)
      expect(stub.callCount).to.eq(1)
    })

    it('caches the decorated method', () => {
      helper.internalState = fromJS({x: {y: 5}})
      expect(helper.testMethod()).to.eq(10)
      expect(helper.testMethod()).to.eq(10)
      expect(stub.callCount).to.eq(1)
    })

    it('stops caching on change', () => {
      helper.internalState = fromJS({x: {y: 5}})
      expect(helper.testMethod()).to.eq(10)
      helper.internalState = fromJS({x: {y: 6}})
      expect(helper.testMethod()).to.eq(12)
      expect(stub.callCount).to.eq(2)
    })

    it('caches by the object calling the method', () => {
      const internalState = fromJS({x: {y: 5}})
      helper.internalState = internalState

      const stub2 = sinon.stub()
      const helper2 = new Helper(stub2)
      helper2.internalState = internalState

      expect(helper.testMethod()).to.eq(10)
      expect(helper2.testMethod()).to.eq(10)
      expect(stub.callCount).to.eq(1)
      expect(stub2.callCount).to.eq(1)
    })

    context('caching by data that lives on the object', () => {
      it('caches normally', () => {
        const internalState = fromJS({id: 'foo', x: {foo: {y: 3}}})
        helper.internalState = internalState
        expect(helper.testMethod3()).to.eq(3)
        expect(helper.testMethod3()).to.eq(3)
        expect(stub.callCount).to.eq(1)
      })
    })
  })
})
