use jest create ut from this vue component: <template>
  <div class="gd-select-box">
    <el-select
      ref="select"
      v-model="comVal"
      class="com-select"
      v-bind="$attrs"
      :remote="remote"
      :remote-method="(remote ? __remoteMethod : undefined)"
      :multiple="comMultiple"
      :reserve-keyword="reserveKeyword"
      :size="size"
      :style="{
        flex: width === '' ? '1' : `0 0 ${parseInt(width)}px`
      }"
      v-on="$listeners"
      @input="__inputHandler"
      @clear="__clearHandler"
      @visible-change="__visibleChangeHandler"
      @remove-tag="__removeTagHandler"
    >
      <el-option
        v-for="(item, index) in comOptions"
        :key="item[idKey]"
        :label="item[textKey]"
        :value="item[idKey]"
        :disabled="item[disabledKey] ? true : false"
      >
        <slot :item="item" :index="index">{{ item[textKey] }}</slot>
      </el-option>
    </el-select>
    <el-button
      v-if="hasRefresh"
      :size="size"
      icon="el-icon-refresh"
      class="refresh-btn"
      round
      @click="__clickFresh"
    />
  </div>
</template>
<script>
import _get from 'lodash-es/get'
import utils from 'src/utils/index.js'
import global from 'src/utils/global.js'

export default {
  name: 'GdSelect',
  inheritAttrs: false,
  data() {
    return {
      emptyValue: '', // el-select清空就是空字符，保持一致
      curLoadIndex: 0, // 加载ID，防止网络慢的情况
      comVal: null,
      comMultiple: false,
      comOptions: [],
      comSource: false,
      comParams: false,
      isFirstLoad: true,
      isInited: false,
      hasValueChange: false,
      hasOpenOption: false
    }
  },

  watch: {
    value: {
      handler(newVal) {
        if (JSON.stringify(this.$data.comVal) !== JSON.stringify(newVal)) {
          if (this.isInited) {
            this.$data.comVal = utils.deepCopy(newVal)
          } else {
            // 初始化时会赋值
          }
        }
      },
      deep: false
    }
  },
  created() {
    this.$data.isFirstLoad = true
    // 是否多选
    this.$data.comVal = this.multiple ? [] : this.$data.emptyValue
    this.$data.comMultiple = !!this.multiple
    this.$data.comParams = utils.deepCopy(this.params)
    this.__reinitSource(this.source)
    if (this.needParams) {
      this.$watch('params', this.__paramsHandler, { deep: true })
    }
  },
  mounted() {
    if (!this.__canLoad()) {
      // 没有接口时，也告诉系统完成
      this.__emitLoaded()
    }
  },
  beforeDestroy() {
    this.$data.curLoadIndex = this.$data.curLoadIndex + 1
  },
  methods: {
    update(source, isParams) {
      if (!isParams) {
        this.__reinitSource(source)
      } else {
        // 强制调用
        this.$data.comParams = source
        this.__load()
      }
    },
    focus() {
      this.$refs.select.focus()
      this.$refs.select.$el.click && this.$refs.select.$el.click()
    },
    clear() {
      if (this.$data.comOptions.length > 0) {
        this.$data.comOptions = []
        if (this.__isEmpty(this.$data.comVal)) {
          this.$data.comVal = this.$data.comMultiple
            ? []
            : this.$data.emptyValue
          this.$emit('input', this.$data.comVal)
        }
      }
    },
    getAllOptions() {
      return this.$data.comOptions
    },
    getCurrent() {
      var currentOption = null
      if (this.$data.comOptions) {
        this.$data.comOptions.forEach((item) => {
          if (this.$data.comVal === item[this.idKey]) {
            currentOption = item
          }
        })
      }
      return currentOption
    },
    __reinitSource(source) {
      var newSource = this.__parseSource(source)
      if (newSource) {
        this.$data.comSource = newSource
        this.__load()
      } else {
        this.$data.comSource = false
        this.$data.comOptions = this.options.slice(0, this.options.length)
        this.__initValue()
      }
    },
    __remoteMethod(query) {
      this.__load(query)
    },
    __clickFresh() {
      this.__load()
    },
    __load(query) {
      ++this.$data.curLoadIndex
      if (this.__canLoad()) {
        // 到这里：comSource肯定有值了
        var queryParams = {}
        if (this.$data.isFirstLoad && this.value) {
          this.$data.isFirstLoad = false
          if (this.firstSearchText) {
            queryParams[this.searchKey] = this.firstSearchText
          } else if (this.firstSearchValue) {
            queryParams[this.searchKey] = this.value
          }
        }
        query = query ? query.trim() : ''
        if (query) {
          queryParams[this.searchKey] = query
        } else if (
          (this.canSearchEmpty || !this.clearEmptyParam) &&
          !queryParams.hasOwnProperty(this.searchKey)
        ) {
          queryParams[this.searchKey] = ''
        }

        if (
          (this.needSearch || !this.canEmptySearch) &&
          !queryParams[this.searchKey]
        ) {
          // 一定要有搜索
          if (!this.isInited) {
            this.$data.comOptions = this.options.slice(0, this.options.length)
            this.__initValue()
          } else {
            // 无需做什么，保持原来的值
          }
          return false
        }

        var source = this.$data.comSource
        var params = utils.deepCopy(source.params)
        params = params || {}

        var comParams = this.$data.comParams
        if (!utils.isObj(comParams)) {
          comParams = {}
        }

        params = Object.assign(params, comParams, queryParams)

        // 过滤请求数据, 包括url, params
        var apiConfig = utils.execRequest(
          source.request,
          this,
          source.url,
          source.method,
          params,
          source.axiosConf
        )
        var curLoadIndex = this.$data.curLoadIndex
        let axiosInstance = global.axios
        axiosInstance(
          apiConfig.url,
          utils.axiosOptions(
            apiConfig.method,
            apiConfig.params,
            apiConfig.axiosConf
          )
        )
          .then(
            (resResult) => {
              if (curLoadIndex !== this.$data.curLoadIndex) {
                return false // 过时了，不用理会
              }

              resResult = utils.getAxiosData(resResult)

              if (source.response) {
                var others = {
                  instance: this, // 实例返回，用户可在箭头函数中使用
                  reqData: params // 未经改造的请求参数
                }
                var tmpResult = source.response.call(this, resResult, others)
                if (tmpResult) {
                  resResult = tmpResult
                }
                others = null
              }

              var newOptions = this.options.slice(0, this.options.length)
              var optionList = source.resField
                ? _get(resResult, source.resField, false)
                : resResult
              if (optionList === false) {
                console.error(
                  'select组件中，取数据列表的索引与接口数据不匹配。当前的索引为:"' +
                    source.resField +
                    '" (若定义了response, 注意是否是response自定义函数的返回值引发的错误)'
                )
                optionList = []
              } else if (optionList === undefined || optionList === null) {
                console.warn(
                  'select组件中，接口数据不匹配，返回的空值不是空数组。当前的索引为:"' +
                    source.resField +
                    '" (若定义了response, 注意是否是response自定义函数的返回值引发的错误)'
                )
                optionList = []
              } else if (!utils.isArr(optionList)) {
                optionList = []
              }
              newOptions = newOptions.concat(optionList)
              if (!this.reserve) { // 不保留原选中的值
                this.$data.comOptions = newOptions
              } else { // 保留原选中的值，新列表没有此值时则添加到后面
                this.$data.comOptions = this.__insertSelected(newOptions, this.$data.comOptions)
              }
              this.__initValue()
              this.__emitLoaded()
            },
            (error) => {
              if (curLoadIndex !== this.$data.curLoadIndex) {
                return false // 过时了，不用理会
              }
              this.$data.comOptions = this.options.slice(
                0,
                this.options.length
              )
              this.__initValue()
              this.__emitLoaded(true, error)
            }
          )
      }
    },

    __canLoad() {
      if (
        (this.needParams && this.$data.comParams === false) ||
        !this.$data.comSource
      ) {
        return false
      } else {
        return true
      }
    },
    __initValue() {
      if (!this.$data.isInited) {
        var comOptions = this.$data.comOptions
        if (comOptions.length > 0) {
          if (this.__isEmpty(this.value)) {
            // 取出选择的个数
            var showNum = 0 // 选择前几个
            if (this.showAll === true) {
              showNum = comOptions.length
            } else if (utils.isNum(this.showAll) && this.showAll > 0) {
              showNum = Math.floor(this.showAll)
            } else if (this.showFirst) {
              showNum = 1
            } else if (this.onlyOne && comOptions.length === 1) {
              showNum = 1
            } else {
              showNum = 0 // 无需要选择
            }
            if (showNum > 0) {
              // 需要选择
              var selectIds = []
              comOptions.forEach((option, index) => {
                if (index < showNum && !option[this.disabledKey]) {
                  selectIds.push(option[this.idKey])
                }
              })
              if (selectIds.length > 0) {
                // 有可能为空，因为返回可能是disabled
                if (this.$data.comMultiple) {
                  this.$data.comVal = selectIds
                } else {
                  this.$data.comVal = selectIds[0] // 必有值，上面已经做了空值判断
                }
                this.$emit('input', this.$data.comVal)
              } else {
                this.$data.comVal = utils.deepCopy(this.value)
              }
            }
          } else {
            this.$data.comVal = utils.deepCopy(this.value)
          }
        } else {
          this.$data.comVal = utils.deepCopy(this.value)
        }
        this.$data.isInited = true
      }
    },

    __emitLoaded(emitError, errorInfo) {
      this.$nextTick(function() {
        this.$emit('loaded')
        if (emitError) {
          this.$emit('error', errorInfo)
        }
      })
    },

    __insertSelected(newOptions, oldOptions) {
      if (!this.__isEmpty(this.comVal)) {
        var values = this.multiple ? this.comVal : [this.comVal]

        // 支掉newOptions已存在的选项
        values = values.filter(value => {
          return !newOptions.some(newOption => {
            return newOption[this.idKey] === value
          })
        })

        // 从oldOptions取出选项补充
        if (values.length > 0) {
          var appendOptions = []
          appendOptions = oldOptions.filter(oldOption => {
            return values.includes(oldOption[this.idKey])
          })
          newOptions = newOptions.concat(utils.deepCopy(appendOptions))
        }
      } else {
        // ...
      }

      return newOptions
    },

    __inputHandler() {
      if (this.$data.hasOpenOption) {
        this.$data.hasValueChange = true // 值已改变
      }
    },

    __visibleChangeHandler(isOpen) {
      this.$data.hasOpenOption = isOpen
      if (!isOpen) {
        if (this.$data.hasValueChange) {
          this.__valueChange()
          this.$data.hasValueChange = false
        }
      }
    },

    __removeTagHandler() {
      if (!this.$data.hasOpenOption) {
        this.__valueChange()
      }
    },

    __clearHandler() {
      if (!this.$data.hasOpenOption) {
        this.__valueChange()
      }
    },

    __valueChange() {
      this.$emit('value-change', this.$data.comVal)
    },

    /**
     * 比较参数是否相同：只比较对象，数组当作字符串比较
     */
    __equalParams(value1, value2) {
      if (value1 === value2) {
        return true
      } else {
        value1 = value1 === true ? {} : value1
        value2 = value2 === true ? {} : value2
        if (utils.isObj(value1) && utils.isObj(value2)) {
          var keys1 = Object.keys(value1)
          var keys2 = Object.keys(value2)
          if (keys1.length === keys2.length) {
            // 属性长度一样
            for (var i = 0; i < keys1.length; i++) {
              var curKey = keys1[i]
              var curResult = true
              if (!value2.hasOwnProperty(curKey)) {
                // 存在此属性
                curResult = false
              } else {
                curResult = this.__equal(value1[curKey], value2[curKey])
              }
              if (!curResult) {
                return false
              }
            }
            return true
          } else {
            return false
          }
        } else {
          return false
        }
      }
    },

    /**
     * 比较某值是否相同
     */
    __equal(value1, value2) {
      if (value1 === value2) {
        return true
      } else if (JSON.stringify(value1) === JSON.stringify(value2)) {
        return true
      } else {
        if (utils.isObj(value1) && utils.isObj(value2)) {
          var keys1 = Object.keys(value1)
          var keys2 = Object.keys(value2)
          if (keys1.length === keys2.length) {
            // 属性长度一样
            for (var i = 0; i < keys1.length; i++) {
              var curKey = keys1[i]
              var curResult = true
              if (!value2.hasOwnProperty(curKey)) {
                // 存在此属性
                curResult = false
              } else {
                curResult = this.__equal(value1[curKey], value2[curKey])
              }
              if (!curResult) {
                return false
              }
            }
            return true
          } else {
            return false
          }
        } else {
          return false
        }
      }
    },

    __isEmpty(value) {
      if (
        utils.isNull(value) ||
        utils.isUndef(value) ||
        (utils.isStr(value) && value === '') ||
        (utils.isArr(value) && value.length === 0)
      ) {
        return true
      } else {
        return false
      }
    },

    __parseSource(source) {
      var newSource = {}
      if (utils.isStr(source)) {
        source = { url: source }
      } else if (!utils.isObj(source) || Object.keys(source).length <= 0) {
        return false
      }

      if (utils.isStr(source.url)) {
        newSource.url = source.url.trim()
      } else {
        throw '数据源不合格，url必须是字符串'
      }

      newSource.method = utils.isStr(source.method)
        ? source.method.trim()
        : 'get'

      newSource.params = utils.isObj(source.params)
        ? utils.deepCopy(source.params)
        : {}

      newSource.resField = utils.isStr(source.resField)
        ? source.resField.trim()
        : 'data'

      if (utils.isFunc(source.request)) {
        newSource.request = source.request
      }

      if (utils.isFunc(source.response)) {
        newSource.response = source.response
      }

      if (utils.isObj(source.axiosConf)) {
        newSource.axiosConf = utils.deepCopy(source.axiosConf)
      }

      return newSource
    },

    __axiosOptions: function(source, params) {
      var options = {}

      var axiosConf = utils.isObj(source.axiosConf) ? source.axiosConf : {}
      options = Object.assign(options, axiosConf)

      var method = utils.isStr(source.method) ? source.method.trim() : 'get'
      if (!method) {
        method = 'get'
      } else {
        method = method.toLowerCase()
      }
      options.method = method

      options[method === 'get' ? 'params' : 'data'] = params

      return options
    },

    __paramsHandler(newVal) {
      if (!this.__equalParams(this.$data.comParams, newVal)) {
        // 参数不一样，重新加载
        this.$data.comParams = utils.deepCopy(newVal)
        this.__load()
      }
    }
  }
}
</script>
<style lang="scss"></style>