All files action.test.ts

99.17% Statements 119/120
100% Branches 0/0
96.55% Functions 28/29
100% Lines 101/101
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 1581x 1x   1x   1x 1x 1x     1x 1x   1x   1x 1x   1x 10x     1x 1x 1x 1x 1x     1x 1x 1x 1x 1x 1x 1x 1x     1x 1x 1x 1x 1x 1x 1x 1x     1x 1x 1x 1x 1x 1x     1x     4x 1x     1x     1x 1x     1x 1x     1x     2x 1x     4x 1x     1x 1x 1x   1x 1x 1x 1x 1x     1x     1x 1x     2x 1x     1x 1x 1x   1x 1x 1x 1x 1x     1x 1x 1x     1x 1x 1x 2x 1x 2x 2x 2x 2x   2x     1x   1x 2x 1x 2x 1x 1x     1x 1x 1x 1x   1x 2x 1x      
import Mocha from 'mocha'
import * as chai from 'chai'
 
import { Action } from './action'
import { Observer } from 'rxjs/Observer'
import { initialize } from './init'
import { Observable } from 'rxjs/Observable'
import { ReplaceableState } from './replaceable-state'
 
// use spies
chai.use(require('chai-spies-next'))
const { expect } = chai
 
describe('Action', () => {
 
  class TestAction extends Action { }
  class UnrelatedAction extends Action { }
 
  beforeEach(() => {
    initialize({})
  })
 
  it('should call a reducer function when an action is dispatched', () => {
    const reducer = chai.spy()
    new TestAction().subscribe(reducer, this)
    new TestAction().dispatch()
    expect(reducer).to.have.been.called.once
  })
 
  it('should call all reducer functions when an action is dispatched', () => {
    const reducer1 = chai.spy()
    const reducer2 = chai.spy()
    new TestAction().subscribe(reducer1, this)
    new TestAction().subscribe(reducer2, this)
    new TestAction().dispatch()
    expect(reducer1).to.have.been.called.once
    expect(reducer2).to.have.been.called.once
  })
 
  it('should call reducer functions subscribed to an action when an action is dispatched', () => {
    const reducer1 = chai.spy()
    const reducer2 = chai.spy()
    new TestAction().subscribe(reducer1, this)
    new UnrelatedAction().subscribe(reducer2, this)
    new TestAction().dispatch()
    expect(reducer1).to.have.been.called.once
    expect(reducer2).not.to.have.been.called.once
  })
 
  it('should call reducer function with state and payload', () => {
    const reducer1 = chai.spy()
    new TestAction().subscribe(reducer1, this)
    const testAction = new TestAction()
    testAction.dispatch()
    expect(reducer1).to.have.been.called.with({}, testAction)
  })
 
  it('should merge the state returned by the reducer function into global state', () => {
 
    // create a reducer function and subscribe to TestAction
    const reducer1 = chai.spy(() => ({ testState: true }))
    new TestAction().subscribe(reducer1, this)
 
    // create an action
    const testAction = new TestAction()
 
    // update the state to { testState: true }
    testAction.dispatch()
    expect(reducer1).to.have.been.called.with({}, testAction)
 
    // checking if updated is made available to second call
    testAction.dispatch()
    expect(reducer1).to.have.been.called.with({ testState: true }, testAction)
  })
 
  it('should partially merge the state returned by the reducer function into global state', () => {
 
    // create a reducer function and subscribe to TestAction
    const reducer1 = chai.spy(() => ({ testState: true }))
    new TestAction().subscribe(reducer1, this)
 
    // create a reducer function and subscribe to TestAction
    const reducer2 = chai.spy(() => ({ anotherState: true }))
    new UnrelatedAction().subscribe(reducer2, this)
 
    // update the state to { testState: true }
    const testAction = new TestAction()
    testAction.dispatch()
    expect(reducer1).to.have.been.called.with({}, testAction)
 
    const unrelatedAction = new UnrelatedAction()
    unrelatedAction.dispatch()
    expect(reducer2).to.have.been.called.with({ testState: true }, unrelatedAction)
    unrelatedAction.dispatch()
    expect(reducer2).to.have.been.called.with({ testState: true, anotherState: true }, unrelatedAction)
  })
 
  it('should replace the entire state if reducer function returns replaceable state', () => {
 
    // create a reducer function and subscribe to TestAction
    const reducer1 = chai.spy(() => ({ testState: true }))
    new TestAction().subscribe(reducer1, this)
 
    // create a reducer function and subscribe to TestAction
    const reducer2 = chai.spy(() => new ReplaceableState({ anotherState: true }))
    new UnrelatedAction().subscribe(reducer2, this)
 
    // update the state to { testState: true }
    const testAction = new TestAction()
    testAction.dispatch()
    expect(reducer1).to.have.been.called.with({}, testAction)
 
    const unrelatedAction = new UnrelatedAction()
    unrelatedAction.dispatch()
    expect(reducer2).to.have.been.called.with({ testState: true }, unrelatedAction)
    unrelatedAction.dispatch()
    expect(reducer2).to.have.been.called.with({ anotherState: true }, unrelatedAction)
  })
 
  it('should not throw an error if an action is called without any subscription', () => {
    class SampleAction extends Action { }
    expect(() => new SampleAction().dispatch()).not.to.throw()
  })
 
  it('should call the returned callback function with current state, if reducer returns a function', async () => {
    initialize({ initialState: true })
    class SampleAction extends Action { }
    const callback = chai.spy(() => ({ testState: true }))
    const reducer1 = chai.spy(() => {
      return Observable.create((observer: Observer<any>) => {
        setTimeout(() => {
          observer.next(callback)
          observer.complete()
        }, 100)
        observer.next({ reducerCalled: true })
      })
    })
    new SampleAction().subscribe(reducer1, this)
 
    const action1 = new SampleAction()
    await action1.dispatch()
    expect(reducer1).to.have.been.called.with({ initialState: true }, action1)
    await action1.dispatch()
    expect(callback).to.have.been.called.with({ initialState: true, reducerCalled: true })
    expect(reducer1).to.have.been.called.with({ initialState: true, reducerCalled: true, testState: true }, action1)
  })
 
  it('should not fail even if the reducer function reject with an error', async () => {
    class SampleAction extends Action { }
    const reducer = chai.spy(() => Promise.reject('Simulated Error'))
    new SampleAction().subscribe(reducer, this)
 
    const errorHandler = chai.spy()
    await new SampleAction().dispatch().then(() => undefined, errorHandler)
    expect(errorHandler).to.have.been.called()
  })
 
})