Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

v1 #12

Open
tunnckoCore opened this issue Nov 14, 2016 · 9 comments
Open

v1 #12

tunnckoCore opened this issue Nov 14, 2016 · 9 comments

Comments

@tunnckoCore
Copy link
Owner

tunnckoCore commented Nov 14, 2016

Let's start it. We have pretty stable libs already. Also it currently silently hides tests that do not call the callback/done. It just normal with this architecture.

So let's clarify what will happen for v1 and what will be the diffs with gruu and asia. At least for me.

mukla v1

  • does not support tests that returns streams and observables (for lower deps and i didnt use them anywhere)
  • core assert methods will remain in the exported instance/function, like currently (test.strictEqual and etc)
  • test function will only be passed with done/callback (if given)
  • detects if defined tests count is equal to runned tests count
  • still compat with mukla v0.x and assertit v0.x
  • this api will improve its testing

gruu v1

  • does not support tests that returns streams/observables
  • core assert methods won't be in the exported instance/function
  • test function will be passed with t argument, where you will find that assertion methods
  • this t argument is object that contains all assert/core-assert methods plus more and will have t.context property object for sharing context between tests (useful when you use arrow functions)
  • if not arrow function the this context of the test function will be same as t, so this.context === t.context
  • won't be compatible with mukla/assertit

asia v1

  • support for anything (it's may not support callback/done argument, like gruu)
  • better assertion methods
  • test function is passed with t argument
  • won't be compatible with mukla/assertit
  • same as gruu v1 and almost compatible with it, plus more
@tunnckoCore
Copy link
Owner Author

tunnckoCore commented Dec 14, 2016

Sort of... It's almost done, really :D It's amazing how minimal is and what it can do.

/*!
 * mukla <https://github.com/tunnckoCore/mukla>
 *
 * Copyright (c) Charlike Mike Reagent <@tunnckoCore> (http://i.am.charlike.online)
 * Released under the MIT license.
 */

'use strict'

var getFnName = require('get-fn-name')
var MiniBase = require('minibase').MiniBase
var results = require('minibase-results')
var flow = require('minibase-control-flow')
var isAsyncFn = require('is-async-function')

function Mukla (options) {
  if (!(this instanceof Mukla)) {
    return new Mukla(options)
  }

  MiniBase.call(this, options)
  this.use(results()) // @TODO: remove `through2`
  this.use(flow())
  this.testNames = []
}

MiniBase.extend(Mukla)

Mukla.prototype.addTest = function addTest (title, fn) {
  if (typeof title === 'function') {
    fn = title
    title = null
  }
  if (typeof fn !== 'function') {
    throw new TypeError('.addTest: expect `fn` to be a function')
  }

  title = typeof title === 'string' ? title : getFnName(fn)
  if (title === null) {
    this.stats.anonymous = this.stats.anonymous + 1
  }

  title = title || '(unnamed test ' + this.stats.anonymous + ')'
  fn.hasCb = isAsyncFn(fn)

  this.testNames.push(title)
  this.tests.push(fn)
  this.stats.count++
  return this
}

Mukla.prototype.run = function run (done) {
  done = typeof done === 'function' ? done : function noop () {}

  var self = this
  var reporter = typeof this.options.reporter === 'function'
    ? this.options.reporter
    : defaultReporter

  reporter.call(this, this)

  this.each(this.tests, this.options, {
    // @TODO: update `each-promise` to use try-catch-core
    // context: this.testContext,
    start: function () {
      self.emit('start')
    },
    beforeEach: function (item, index, arr) {
      self.emit('beforeEach', item, index, arr)
    },
    afterEach: function (item, index, arr) {
      self.stats.runned++
      self.emit('afterEach', item, index, arr)

      var fn = arr[index]
      var test = {
        fn: fn,
        hasCb: fn.hasCb,
        reason: item.reason,
        value: item.value,
        index: item.index,
        title: self.testNames[index],
        tests: arr
      }

      if (item.reason) {
        self.stats.fail++
        return self.emit('fail', test, test.index + 1)
      }
      console.log(fn.hasCb)
      // @TODO: update `each-promise` to use try-catch-core
      if (fn.hasCb && item.value) {
        var msg = ' must not return anything, it has `done` argument'
        test.reason = new Error('test ' + (test.index + 1) + msg)
        self.stats.fail++
        return self.emit('fail', test, test.index + 1)
      }
      if (!fn.hasCb && !item.value) {
        var msg = ' must return, it has not `done` argument'
        test.reason = new Error('test ' + (test.index + 1) + msg)
        self.stats.fail++
        return self.emit('fail', test, test.index + 1)
      }
      self.stats.pass++
      self.emit('pass', test, index + 1)
    },
    finish: function (err, res) {
      self.emit('finish', err, res, done)
    }
  }).catch(done)
}

function defaultReporter () {
  this
    .once('start', function () {
      console.log('TAP version 13')
      console.log('1..' + this.stats.count)
    })
    .on('pass', function (test, idx) {
      console.log('ok', idx, test.title)
    })
    .on('fail', function (test, idx) {
      console.log('not ok', idx, test.title, test.reason.toString())
    })
    .once('finish', function (err, res, done) {
      if (!done) return console.log('done')
      if (err) return done(err)
      done(null, res)
    })
}

/**
 * example usage
 */

var delay = require('delay')
var fixtureTwo = function () {
  return [
    () => delay(900).then(() => 1),
    () => delay(770).then(() => { throw new Error('foo') }),
    () => delay(620).then(() => 3),
    () => delay(800).then(() => {}),
    () => delay(700).then(() => 5),
    (cb) => {
    }
  ]
}

var app = Mukla(/*{ serial: true }*/)

fixtureTwo().forEach(function (fn) {
  app.addTest(fn)
})

app.run(console.log)

@tunnckoCore
Copy link
Owner Author

blocked by each-promise update (and the workflow around Start + Rollup/Rolldown + Buble)

@tunnckoCore
Copy link
Owner Author

Rolldown CLI is needed too, so we'll have Mukla CLI that transpiles ESM.

@tunnckoCore
Copy link
Owner Author

gruu/mukla/asia/testup/voala, using p-map, mitt (tunnckoCore/mitt, refactor branch)

const getFnName = require('get-fn-name')
const pMap = require('p-map')
const mitt = require('./mitt')

function defaultReporter () {
  const emitter = mitt()
  emitter
    .on('start', ({ stats }) => {
      console.log('TAP version 13')
      console.log(`1..${stats.count}`)
    })
    .on('afterEach', ({ test }) => {
      if (test.reason) {
        console.log('not ok', test.index, test.title, test.reason.toString())
      } else {
        console.log('ok', test.index, test.title)
      }
    })
    .on('finish', ({ reason, stats }) => {
      if (reason) {
        console.log('# NOT OK', stats)
        process.exit(1)
      } else {
        console.log('# OK', stats)
        process.exit(0)
      }
      // console.log('# OK', stats)
      // process.exit(0)
    })
  return emitter
}

function handleFn (fn, opts) {
  return new opts.Promise(function (resolve, reject) {
    var called = false

    function done (e, res) {
      called = true
      if (e) return reject(e)
      if (res instanceof Error) {
        return reject(res)
      }
      return resolve(res)
    }

    var args = utils.arrayify(opts.args)
    args = fn.length ? args.concat(done) : args

    var ret = fn.apply(opts.context, args)

    if (!called) {
      ret instanceof Error
        ? reject(ret)
        : resolve(ret)
    }
  })
}

function testup (options) {
  options = Object.assign({}, options)

  const app = {
    stats: {
      anonymous: 0,
      count: 0,
      fail: 0,
      pass: 0
    },
    options: options,
    tests: [],
    addTest: (title, fn) => {
      if (typeof title === 'function') {
        fn = title
        title = null
      }
      if (typeof fn !== 'function') {
        throw new TypeError('.addTest: expect `fn` to be a function')
      }

      title = typeof title === 'string' ? title : getFnName(fn)
      if (title === null) {
        app.stats.anonymous = app.stats.anonymous + 1
      }

      app.stats.count++
      app.tests.push({
        title: title || `(unnamed test ${app.stats.anonymous})`,
        index: app.stats.count,
        fn: fn
      })

      return app
    },
    addReporter: (fn) => {
      app.reporter = fn()
      return app.reporter
    },
    run: (opts) => {
      app.options = Object.assign({}, app.options, opts)
      if (typeof app.reporter !== 'function') {
        app.reporter = app.addReporter(defaultReporter)
      }

      app.reporter.emit('start', { stats: app.stats })

      const mapper = (test) => {
        app.reporter.emit('beforeEach', { test: test, stats: app.stats })
        return handleFn(test.fn, app.options)
          .then((val) => {
            test.value = val
            app.stats.pass++
            app.reporter.emit('afterEach', { test: test, stats: app.stats })
            return val
          })
          .catch((er) => {
            test.reason = er
            app.stats.fail++
            app.reporter.emit('afterEach', { test: test, stats: app.stats })
            throw er
          })
      }

      return pMap(app.tests, mapper, app.options).then(() => {
        app.reporter.emit('finish', { stats: app.stats })
      }, (err) => {
        app.reporter.emit('finish', { reason: err, stats: app.stats })
      })
    }
  }

  return app
}

/**
 * EXAMPLE
 */

var delay = require('delay')
var assert = require('assert')
const runner = testup()



setTimeout(() => {
  runner.run()
}, 0)

runner.addTest('foo bar baz', function () {
  assert.strictEqual(1, 1)
})
runner.addTest('quxie setty', function () {
  return delay(400).then(() => {
    assert.strictEqual(2222, 222)
  })
})
runner.addTest('zeta gama', function () {
  sasa.strictEqual(3, 3)
})
runner.addTest('hiahahah zeah', function () {
  assert.strictEqual(3, 3)
})

@tunnckoCore
Copy link
Owner Author

tunnckoCore commented Mar 7, 2017

So, let's finalize it?

Mukla v1 - minibase + each-promise (possibly node<4, through native-promise)
Gruu v1 - p-map + mitt (Node>=4, ES6 only), minimalist, functional (above)
Asia v1 - batteries included (promises, observables, streams, etc), more extensible - plugins, better API

@tunnckoCore
Copy link
Owner Author

tunnckoCore commented Apr 29, 2017

backup

minibase v2 and test runner

minibase

'use strict'
var dush = require('dush')
var dushOptions = require('dush-options')
var dushBetterUse = require('dush-better-use')

/**
 * dush plugins
 */

var redolent = require('redolent')
var mixinDeep = require('mixin-deep')
var createPlugin = require('minibase-create-plugin')

var dushPlugins = createPlugin('dush-plugins', function (app, opts) {
  app.options = mixinDeep({}, app.options, opts)

  app.use(dushBetterUse())

  app.define('run', function run (a, b, c) {
    var args = [].slice.call(arguments)
    var resolve = redolent(function resolver () {
      app.emit('start', app)
    }, app.options)

    return app._plugins
      .reduce(reducer(app, args), resolve())
      .then(function () {
        app.emit('finish', app)
      })
      .catch(function (err) {
        app.emit('error', err)
        app.emit('finish', app)
      })
  })

  return app
})

function reducer (app, args) {
  return function reducer (promise, handler) {
    return promise.then(function () {
      app.emit('beforeEach', app, handler, args)
      return redolent(handler, {
        Promise: app.options.Promise,
        context: app.options.context || handler,
        args: args
      })()
        .then(function (res) {
          handler.value = res
          app.emit('afterEach', app, handler)
        })
        .catch(function (err) {
          handler.reason = err
          app.emit('afterEach', app, handler)
        })
    })
  }
}

/**
 * MiniBase v2
 */

function MiniBase (opts) {
  return dush()
    .use(dushOptions(opts))
    .use((app) => {
      app._plugins = []
      app.addTest = (title, fnc) => {
        fnc.title = title
        fnc.index = app._plugins.length + 1

        app._plugins.push(fnc)
        return app
      }
    })
    .use((app) => {
      app.on('afterEach', (app, test) => {
        if (test.reason) {
          app.options.stats.count++
          app.options.stats.fail++
          app.emit('fail', app, test)
        } else {
          app.options.stats.count++
          app.options.stats.pass++
          app.emit('pass', app, test)
        }
      })
    })
    .use(dushBetterUse())
    .use(dushPlugins())
}

module.exports = MiniBase

runner

'use strict'
var minibase = require('./minibase')
var tapReport = require('dush-tap-report')

var extend = require('extend-shallow')
var assert = require('assert')

function Mukla (opts) {
  return minibase(opts).use(
    tapReport({
      stats: {
        count: 0,
        pass: 0,
        fail: 0
      }
    })
  )
}

var app = Mukla({
  relativePaths: false // strange bug when `true`
})

function test (title, fn) {
  if (title && typeof title === 'object') {
    app.options = extend({}, app.options, title)
  } else {
    app.addTest(title, fn)
  }

  return test
}

setTimeout(function () {
  var done = function done (er) {
    return new Promise(function (resolve, reject) {
      if (er) return reject(er)
      resolve()
    })
  }

  // passed to each test function
  // backward compatible: passes `done` function
  // that has `assert` methods on it too 
  var param = extend(done, assert)

  app.run(param).then(() => {
    if (process && process.versions['electron']) {
      window.close()
    }
  })
}, 0)

module.exports = extend(test, app, assert)

@tunnckoCore
Copy link
Owner Author

tunnckoCore commented May 22, 2018

Damn.. Love myself when document everything 🍡

@tunnckoCore
Copy link
Owner Author

tunnckoCore commented Jul 2, 2019

Oh hell yea, but in current times it starting to lose more and more sense. Anyway, there is pretty stable implementation at asia@next (the pre-v1) too.

I'm not sure with what and when I will finally come up. But it was a wild ride through the years. A lot of experiments and learned things.

@tunnckoCore
Copy link
Owner Author

Yep. After 3 years out, i read that thing yet again :D

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant