kahlan/kahlan

View on GitHub
src/Block/Specification.php

Summary

Maintainability
A
2 hrs
Test Coverage
A
100%
<?php
namespace Kahlan\Block;

use Kahlan\Block;
use Closure;
use Exception;
use Kahlan\Expectation;
use Kahlan\Log;
use Kahlan\Scope\Specification as Scope;
use Kahlan\Suite;

class Specification extends Block
{
    /**
     * List of expectations.
     * @var Expectation[]
     */
    protected $_expectations = [];

    /**
     * Constructor.
     *
     * @param array $config The Suite config array. Options are:
     *                      -`'closure'` _Closure_ : the closure of the test.
     *                      -`'message'` _string_  : the spec message.
     *                      -`'scope'`   _string_  : supported scope are `'normal'` & `'focus'`.
     */
    public function __construct($config = [])
    {
        $defaults = [
            'message' => 'passes'
        ];
        $config += $defaults;
        $config['message'] = 'it ' . $config['message'];

        parent::__construct($config);

        $this->_scope = new Scope(['block' => $this]);
        $this->_closure = $this->_bindScope($this->_closure);
    }

    /**
     * Reset the specification.
     *
     * @return Expectation
     */
    public function reset()
    {
        $this->_passed = null;
        $this->_expectations = [];
        $this->_log = new Log([
            'block' => $this,
            'backtrace' => $this->_backtrace
        ]);
        return $this;
    }

    /**
     * The assert statement.
     *
     * @param array $config The config array. Options are:
     *                       -`'actual'`  _mixed_   : the actual value.
     *                       -`'timeout'` _integer_ : the timeout value.
     *                       Or:
     *                       -`'handler'` _Closure_ : a delegated handler to execute.
     *                       -`'type'`    _string_  : delegated handler supported exception type.
     *
     * @return Expectation
     */
    public function assert($config = [])
    {
        return $this->_expectations[] = new Expectation($config);
    }

    /**
     * The expect statement (assert shortcut).
     *
     * @param  mixed       $actual The expression to check
     *
     * @return Expectation
     */
    public function expect($actual, $timeout = 0)
    {
        return $this->_expectations[] = new Expectation(compact('actual', 'timeout'));
    }

    /**
     * The waitsFor statement.
     *
     * @param  mixed $actual The expression to check
     *
     * @return mixed
     */
    public function waitsFor($actual, $timeout = 0)
    {
        $timeout = $timeout ?: $this->timeout();
        $closure = $actual instanceof Closure ? $actual : function () use ($actual) {
            return $actual;
        };
        $spec = new static(['closure' => $closure]);

        return $this->expect($spec, $timeout);
    }

    /**
     * Spec execution helper.
     */
    protected function _execute()
    {
        $result = null;
        $spec = function () {
            $this->_expectations = [];
            $closure = $this->_closure;
            $result = $this->_suite->runBlock($this, $closure, 'specification');
            foreach ($this->_expectations as $expectation) {
                $this->_passed = $expectation->process() && $this->_passed;
            }
            return $result;
        };

        $suite = $this->suite();
        return $spec();
    }

    /**
     * Start spec execution helper.
     */
    protected function _blockStart()
    {
        $this->report('specStart', $this);
        if ($this->_parent) {
            $this->_parent->runCallbacks('beforeEach');
        }
    }

    /**
     * End spec execution helper.
     */
    protected function _blockEnd($runAfterEach = true)
    {
        $type = $this->log()->type();
        foreach ($this->_expectations as $expectation) {
            if (!($logs = $expectation->logs()) && $type !== 'errored') {
                $this->log()->type('pending');
            }
            foreach ($logs as $log) {
                $this->log($log['type'], $log);
            }
        }

        if ($type === 'passed' && empty($this->_expectations)) {
            $this->log()->type('pending');
        }
        $type = $this->log()->type();

        if ($type === 'failed' || $type === 'errored') {
            $this->_passed = false;
            $this->suite()->failure();
        }

        if ($this->_parent && $runAfterEach) {
            try {
                $this->_parent->runCallbacks('afterEach');
            } catch (Exception $exception) {
                $this->_exception($exception, true);
            }
        }

        $this->summary()->log($this->log());

        $this->report('specEnd', $this->log());

        Suite::current()->scope()->clear();
        $this->suite()->autoclear();
    }

    /**
     * Returns execution log.
     *
     * @return array
     */
    public function logs()
    {
        $logs = [];
        foreach ($this->_expectations as $expectation) {
            foreach ($expectation->logs() as $log) {
                $logs[] = $log;
            }
        }
        return $logs;
    }
}