All files ThroughProvider.js

100% Statements 50/50
87.18% Branches 34/39
100% Functions 13/13
100% Lines 49/49

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 136 137 138 139 140                              14x 14x       30x                     154x 14x             154x       46x   46x 37x         13x   13x 13x   13x 13x 13x           107x 107x               12x                 50x 49x   49x 49x 1x       48x   48x   27x 3x     27x 27x 27x 27x       28x 27x 27x   27x 27x 2x               19x 19x 19x 19x 19x   19x 19x 19x 19x 19x 19x         30x      
import React, { Children } from 'react'
import PropTypes from 'prop-types'
import hasDiff from './hasDiff'
import hasComplex from './hasComplex'
 
export default class ThroughProvider extends React.Component {
  static childContextTypes = {
    through: PropTypes.object.isRequired,
  }
 
  static propTypes = {
    children: PropTypes.element,
  }
 
  constructor(props) {
    super(props)
    this.areas = {}
  }
 
  getChildContext() {
    return {
      through: {
        update: this.update,
        add: this.add,
        remove: this.remove,
        subscribe: this.subscribe,
      }
    }
  }
 
  area = (area) => {
    if( !Object.prototype.hasOwnProperty.call(this.areas, area) ) {
      this.areas[area] = {
        name: area,
        listeners: [],
        counters: {},
        data: {},
      }
    }
    return this.areas[area]
  }
 
  notify = (area, syncUpdate) => {
    area = this.area(area)
 
    area.listeners.forEach(
      listener => listener(area.data, syncUpdate)
    )
  }
 
  subscribe = ( area, listener ) => {
    area = this.area(area)
 
    area.listeners.push(listener)
    listener(area.data)
 
    return () => {
      area.listeners = area.listeners.filter(
        item => item !== listener
      )
    }
  }
 
  checkArgs(area, key, props) {
    Eif (process.env.NODE_ENV !== 'production') {
      if(
        !( typeof area === 'string' || area instanceof String ) ||
        !( typeof key === 'string' || key instanceof String ) ||
        !( props instanceof Object &&
          !(props instanceof Array) &&
          !(props instanceof Function)
        )
      ) {
        throw new Error(
          "react-through: type error: " +
          "through.[add|update|remove](area:string, key:string, props:Object)"
        )
      }
    }
  }
 
  update = (area, key, props, syncUpdate = undefined) => {
    this.checkArgs(area, key, props)
    area = this.area(area)
 
    Eif (process.env.NODE_ENV !== 'production') {
      if( !area.counters[key] ) {
        throw new Error("react-through: bearing key must be added before update")
      }
    }
 
    const prevProps = area.data[key] || {}
 
    if( !hasDiff(prevProps, props) ) return
 
    if( undefined === syncUpdate ) {
      syncUpdate = !hasComplex(props)
    }
 
    const data = Object.assign({}, area.data)
    data[key] = {...props}
    area.data = data
    this.notify(area.name, syncUpdate)
  }
 
  add = (area, key) => {
    this.checkArgs(area, key, {})
    area = this.area(area)
    area.counters[key] = area.counters[key] ? area.counters[key] + 1 : 1
 
    Eif (process.env.NODE_ENV !== 'production') {
      if( 2 < area.counters[key] ) {
        throw new Error(
          "react-through: bearing key adding must be not more 2, call remove()"
        )
      }
    }
  }
 
  remove = (area, key) => {
    this.checkArgs(area, key, {})
    area = this.area(area)
    let count = area.counters[key]
    count = count ? count - 1 : 0
    area.counters[key] = count
 
    Eif( !count && Object.prototype.hasOwnProperty.call(area.data, key) ) {
      const data = Object.assign({}, area.data)
      delete area.counters[key]
      delete data[key]
      area.data = data
      this.notify(area.name, true)
    }
  }
 
  render() {
    return React.Children.only(this.props.children)
  }
}