TestGroup.bs

namespace rooibos
  class TestGroup
    'test state
    name = "Unnamed Suite"

    testSuite = invalid
    setupFunctionName = invalid
    tearDownFunctionName = invalid
    beforeEachFunctionName = invalid
    afterEachFunctionName = invalid
    isSolo = false
    isLegacy = false
    isIgnored = false
    stats = invalid
    scene = invalid
    lineNumber = 00
    top = invalid
    valid = false
    hasFailures = false
    isNodeTest = false
    nodeName = invalid
    testsData = invalid
    tests = []
    public deferred = invalid

    function new(testSuite, data)
      m.testSuite = testSuite
      m.name = data.name
      m.valid = data.valid
      m.hasFailures = testSuite.hasFailures
      m.isSolo = data.isSolo
      m.isIgnored = data.isIgnored
      m.isAsync = data.isAsync
      m.asyncTimeout = data.asyncTimeout
      m.testsData = data.testCases
      m.isNodeTest = testSuite.isNodeTest
      m.nodeName = invalid
      m.setupFunctionName = data.setupFunctionName
      m.tearDownFunctionName = data.tearDownFunctionName
      m.beforeEachFunctionName = data.beforeEachFunctionName
      m.afterEachFunctionName = data.afterEachFunctionName
      m.lineNumber = data.lineNumber

      if m.isNodeTest
        m.deferred = rooibos.promises.create()
      end if

      'bs:disable-next-line
      m.global = testSuite.global
      m.top = testSuite.top
      m.scene = testSuite.scene
      m.stats = new rooibos.Stats()
    end function

    '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
    '++ running
    '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

    private currentTestIndex = 0
    'TODO CONVERT THIS TO ASYNC
    function run()
      rooibos.common.logTrace(">>>>>>>>>>>>")
      rooibos.common.logTrace("RUNNING TEST GROUP")
      m.testRunner = m.testSuite.testRunner
      m.notifyReportersOnTestGroupBegin()
      if m.testSuite.isNodeTest = true
        rooibos.common.logTrace("THIS GROUP IS ASYNC")
        m.runAsync()
        return m.deferred
      else
        rooibos.common.logTrace("THIS GROUP IS SYNC")
        m.runSync()
        return true
      end if
    end function

    function runSync()
      isOk = m.runSuiteFunction(m.setupFunctionName, "setup")

      if isOk
        for each testData in m.testsData
          test = new rooibos.Test(m, testData)
          m.tests.push(test)

          if test.isIgnored
            m.notifyReportersOnTestBegin(test)
            m.testSuite.runTest(test)
            m.notifyReportersOnTestComplete(test)
            m.stats.appendTestResult(test.result)
            continue for
          end if

          isOk = m.runSuiteFunction(m.beforeEachFunctionName, "beforeEach", test)

          if isOk
            m.notifyReportersOnTestBegin(test)
            m.testSuite.runTest(test)
            m.notifyReportersOnTestComplete(test)
          end if

          m.runSuiteFunction(m.afterEachFunctionName, "afterEach", test)

          m.stats.appendTestResult(test.result)

          if m.stats.hasFailures and m.testSuite.isFailingFast
            rooibos.common.logTrace("Terminating group due to failed test")
            exit for
          end if
        end for
      else
        rooibos.common.logError("ERROR running test setup function")
      end if
      m.notifyReportersOnTestGroupComplete()
      m.runSuiteFunction(m.tearDownFunctionName, "tearDown")
    end function


    function runAsync()
      isOk = m.runSuiteFunction(m.setupFunctionName, "setup")
      m.testTimer = createObject("roTimespan")

      if isOk
        m.testRunner.currentGroup = m
        for each testData in m.testsData
          test = new rooibos.Test(m, testData)
          m.tests.push(test)
        end for
        m.currentTestIndex = -1
        m.runNextAsync()
      else
        rooibos.common.logError("ERROR running test setup function")
        m.runSuiteFunction(m.tearDownFunctionName, "tearDown")
      end if
    end function

    private function runNextAsync()
      rooibos.common.logTrace("Getting next async test")
      m.currentTestIndex++
      m.currentTest = m.tests[m.currentTestIndex]
      m.testSuite.isDoneCalled = false
      m.testTimer.mark()
      if m.currentTest = invalid
        rooibos.common.logTrace("All tests are finished")
        m.finishAsyncTests()
      else
        test = m.currentTest
        ' Check to see if the test is ignored or if the suite is timed out
        ' and skip the before and after hooks
        if test.isIgnored or m.testSuite.isSuiteTimedOut()
          m.notifyReportersOnTestBegin(test)
          m.testSuite.runTest(test)
          m.onAsyncTestComplete()
          return invalid
        end if

        isOk = m.runSuiteFunction(m.beforeEachFunctionName, "beforeEach", m.currentTest)
        if isOk
          m.notifyReportersOnTestBegin(test)

          m.testSuite.runTest(test)
          rooibos.common.logDebug("Waiting on deferred test results")

          rooibos.promises.chain(test.deferred, { self: m, test: test }).then(function(_, context)
            rooibos.common.logDebug("Promise resolved")
            context.self.testSuite.done()
          end function).catch(function(error, context)
            rooibos.common.logDebug("Promise rejected")
            if rooibos.common.isAssociativeArray(error) and not error.isEmpty() and rooibos.common.isArray(error.backtrace) and not error.backtrace.isEmpty() and rooibos.common.isBoolean(error.rethrown)
              context.self.testSuite.failCrash(error)
            else
              context.self.testSuite.fail("Test failed due to promise rejection")
            end if
            context.self.testSuite.done()
          end function).finally(function(context)
            context.self.onAsyncTestComplete()
          end function)
        else
          rooibos.common.logTrace("Error running test before each function")
          m.isTestFailedDueToEarlyExit = true
          m.finishAsyncTests()
        end if
      end if
    end function

    private function onAsyncTestComplete()
      rooibos.common.logTrace("++ CURRENT TEST COMPLETED")
      m.notifyReportersOnTestComplete(m.currentTest)
      m.runSuiteFunction(m.afterEachFunctionName, "afterEach", m.currentTest)
      m.stats.appendTestResult(m.currentTest.result)

      if m.stats.hasFailures and m.testSuite.isFailingFast
        rooibos.common.logTrace("Terminating group due to failed test")
        m.isTestFailedDueToEarlyExit = true
        m.finishAsyncTests()
      else
        m.runNextAsync()
      end if

    end function

    private function finishAsyncTests()
      m.runSuiteFunction(m.tearDownFunctionName, "tearDown")
      rooibos.common.logTrace("Indicating test suite is done")
      m.notifyReportersOnTestGroupComplete()
      rooibos.promises.resolve(true, m.deferred)
    end function

    private function runSuiteFunction(methodName, defaultMethodName, test = invalid)

      if methodName = invalid or methodName = ""
        methodName = defaultMethodName
      end if
      if m.testSuite.catchCrashes and not m.testSuite.noCatch and not (test <> invalid and test.noCatch)
        ' Add the users suite functions execution time to the suites current execution time
        timespan = createObject("roTimespan")
        try
          m.testSuite[methodName]()
          m.testSuite.currentExecutionTime += timespan.totalMilliseconds()
          return true
        catch error
          if test <> invalid
            'bs:disable-next-line
            test.result.crash("function " + methodName + "crashed!", error)
            m.testSuite.currentExecutionTime += timespan.totalMilliseconds()
          end if
        end try
      else
        timespan = createObject("roTimespan")
        m.testSuite[methodName]()
        m.testSuite.currentExecutionTime += timespan.totalMilliseconds()
        return true
      end if

      'bs:disable-next-line
      return false
    end function

    private sub notifyReportersOnTestGroupBegin()
      for each reporter in m.testSuite.testReporters
        if rooibos.common.isFunction(reporter.onTestGroupBegin)
          reporter.onTestGroupBegin({ group: m })
        end if
      end for
    end sub

    private sub notifyReportersOnTestBegin(test as rooibos.Test)
      for each reporter in m.testSuite.testReporters
        if rooibos.common.isFunction(reporter.onTestBegin)
          reporter.onTestBegin({ test: test })
        end if
      end for
    end sub

    private sub notifyReportersOnTestComplete(test as rooibos.Test)
      if test.result.time > 0
        ' Add the test execution time to the suites current execution time
        m.testSuite.currentExecutionTime += test.result.time
      end if

      for each reporter in m.testSuite.testReporters
        if rooibos.common.isFunction(reporter.onTestComplete)
          reporter.onTestComplete({ test: test })
        end if
      end for
    end sub

    private sub notifyReportersOnTestGroupComplete()
      for each reporter in m.testSuite.testReporters
        if rooibos.common.isFunction(reporter.onTestGroupComplete)
          reporter.onTestGroupComplete({ group: m })
        end if
      end for
    end sub
  end class
end namespace