| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169 | //@flow
let tests: Array<TestCase> = []
let RCTContext: ReactElement = null
let props:any = {}
let timeout = 8000
export default class TestContext {
  static setTimeout (val) {
    timeout = val
  }
  static config(config) {
    return TestContext.describe.bind(config)
  }
  /**
   * Calling this method will push a test case into task queue.
   * @param  {String}   desc Description of test case.
   * @param  {Function:Promise<any>} fn   Body of test case, this function
   *         should return a promise.
   * @return {void}
   */
  static describe (...args) {
    let { group, timeout, expand, run } = this || {}
    let desc, config, fn
    if([...args].length === 2) {
      [desc, fn] = [...args]
    }
    else if ([...args].length === 3) {
      [desc, config, fn] = [...args]
      group = config.group || group
      timeout = config.timeout || timeout
      expand = config.expand || expand
      run = config.run || run
    }
    let ctx = {
      group : group || 'common',
      status : 'waiting',
      run : run === false ? false : true,
      result : null,
      asserts : [],
      timeout : timeout || 3000,
      expired : false,
      running : false,
      executed : false,
      expand : expand || false,
      desc,
      fn,
      sn : tests.length,
      start : (i) => {
        TestContext.startTest.bind(
          tests[i],
          TestContext.update.bind(TestContext, i),
          TestContext.updateInternal.bind(TestContext, i)
        )()
      }
    }
    tests.push(ctx)
  }
  static prop (name:string, val:any):TestContext {
    if(name === undefined && val === undefined)
      return props
    if(val === undefined)
      return props[name]
    props[name] = val
    return TestContext
  }
  /**
   * Run test cases in sequence.
   * @param  {ReactElement} context ReactElement instance context.
   * @return {void}
   */
  static run (context:ReactElement) {
    RCTContext = context
    let promise = Promise.resolve()
    // run test case sequently
    for(let i in tests) {
      if(tests[i].run === false) {
        tests[i].status = 'skipped'
        tests[i].executed = true
        promise = Promise.resolve()
        continue
      }
      promise = promise.then(
        TestContext.startTest.bind(
          tests[i],
          TestContext.update.bind(TestContext, i),
          TestContext.updateInternal.bind(TestContext, i)
      ))
    }
    return promise
  }
  static startTest(update, updateInternal, data) {
    return new Promise((resolve, reject) => {
      let expired = false
      updateInternal({
        running : true,
      })
      // set timeout timer
      let tm = setTimeout(() => {
        updateInternal({
          expired : true,
          executed : true,
          running : false
        })
        resolve('ETIMEOUT')
      }, this.timeout)
      // run test body
      new Promise((done) => {
        this.fn.bind(this)(update, done)
      })
      .then((...res) => {
        if(!expired) {
          clearTimeout(tm)
          updateInternal({
            executed : true,
            running : false
          })
          resolve(...res)
        }
      }).catch((err) => {
        updateInternal({
          executed : true,
          running : false
        })
      })
    })
  }
  /**
   * Update test task result of given index.
   * @param  {number} i       Index of test case to be updated.
   * @param  {ReactElement<Info | Assert>} ...data Assertion or Info of test.
   * @return {void}
   */
  static update(i, ...data) {
    let test = tests[i]
    let result = test.result || []
    Object.assign(test, {result : [...result, ...data]})
    RCTContext.forceUpdate()
  }
  static getTests() {
    return tests
  }
  /**
   * Update test result for testkit internal use
   * @param  {[type]} i      Index of test case to be updated.
   * @param  {TestCaseContext} result Test case object
   * @return {void}
   */
  static updateInternal(i, result) {
    Object.assign(tests[i], result)
    RCTContext.forceUpdate()
  }
}
 |