All files index.js

100% Statements 61/61
100% Branches 16/16
100% Functions 20/20
100% Lines 60/60

Press n or j to go to the next uncovered block, b, p or k for the previous block.

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            2x 2x       14x 14x 14x 14x 14x       27x 16x 16x         2x 2x       7x 7x   7x 3x   4x     7x   7x 7x 8x 8x 8x 6x   8x                   14x 14x 14x 14x 14x 14x     10x 11x 9x     1x       2x 2x   2x       13x 13x       8x       24x 2x     22x   22x 27x 13x 13x 13x     14x 14x 14x 14x     22x 22x       23x 24x             11x     11x 11x            
import React from 'react'
import createReactContext from 'create-react-context'
import produce from 'immer'
 
 
 
const Context = createReactContext()
let ListenerID = 0
 
export class Controller {
  constructor() {
    this.state = {}
    this.setFaterState = []
    this.produce = produce
    this.isDirty = false
    this.map = new Map()
  }
 
  addListener = (setState, id) => {
    if (!this.map.get(id)) {
      this.map.set(id, true)
      this.setFaterState.push({ fn: setState, id: id })
    }
  }
 
  removeListener = id => {
    this.map.delete(id)
    this.setFaterState = this.setFaterState.filter(i => i.id !== id)
  }
 
  setState(partial, cb) {
    this.isDirty = true
    setImmediate(() => {
      let newState
      if (typeof partial === 'function') {
        newState = produce(this.state, partial)
      } else {
        newState = { ...this.state, ...partial }
      }
 
      this.state = newState
 
      let callback_length = this.setFaterState.length
      this.setFaterState.forEach(({ fn, id }) => {
        fn(this.state, () => {
          callback_length--
          if (callback_length <= 0) {
            this.isDirty = false
          }
          cb && cb(this.state)
        })
      })
      // no matter what `newState` is, we have to set state to original state
    })
  }
}
 
export class Listen extends React.Component {
  constructor(props) {
    ListenerID++
    super(props)
    this.id = ListenerID
    this.state = {}
    this.Machines = []
    this._isMounted = false
  }
  shouldComponentUpdate() {
    for (let i in this.Machines) {
      if (this.Machines[i].isDirty === true) {
        return true
      }
    }
    return false
  }
 
  componentWillUnmount() {
    this.Machines.forEach(m => {
      m.removeListener(this.id)
    })
    this._isMounted = false
  }
 
  componentDidMount() {
    this._isMounted = true
    this.props.didMount && this.props.didMount.apply(null, this.Machines)
  }
 
  _noopUpdate = (state, cb) => {
    this.setState({}, cb)
  }
 
  createMachine = context => {
    if (context === void 666) {
      throw new Error('<Listen/> components must be wrapped in a <Provider/>')
    }
 
    const MachineConstructor = this.props.to
 
    let newMachines = MachineConstructor.map(Machine => {
      if (context.get(Machine)) {
        const instance = context.get(Machine)
        instance.addListener(this._noopUpdate.bind(this), this.id)
        return instance
      }
 
      let newInstance = new Machine()
      newInstance.addListener(this._noopUpdate.bind(this), this.id)
      context.set(Machine, newInstance)
      return newInstance
    })
 
    this.Machines = newMachines
    return this.Machines
  }
 
  render() {
    return (
      <Context.Consumer>{context => this.props.children.apply(null, this.createMachine(context))}</Context.Consumer>
    )
  }
}
 
export class Provider extends React.Component {
  render() {
    return (
      <Context.Consumer>
        {topState => {
          let childState = new Map(topState)
          return <Context.Provider value={childState}>{this.props.children}</Context.Provider>
        }}
      </Context.Consumer>
    )
  }
}