From 795a77e7850d19c0c6f094ec0b7902b1ac1af50b Mon Sep 17 00:00:00 2001 From: Erin Millard Date: Fri, 18 Nov 2016 16:13:53 +1000 Subject: [PATCH] Implemented focused tests. Closes #185. --- fixtures/focused.spec.php | 66 ++++++++++++++++++++++++++++++++++++++ specs/context.spec.php | 28 +++++++++++++--- specs/suite.spec.php | 64 ++++++++++++++++++++++++++++++++++++ specs/suiteloader.spec.php | 2 +- specs/test.spec.php | 7 +++- src/Core/AbstractTest.php | 9 +++++- src/Core/Suite.php | 38 ++++++++++++++++++++-- src/Core/Test.php | 14 ++++++-- src/Core/TestInterface.php | 7 ++++ src/Dsl.php | 35 +++++++++++++++++++- src/Runner/Context.php | 29 ++++++++++------- src/Test/ItWasRun.php | 5 +-- 12 files changed, 278 insertions(+), 26 deletions(-) create mode 100644 fixtures/focused.spec.php diff --git a/fixtures/focused.spec.php b/fixtures/focused.spec.php new file mode 100644 index 0000000..c04fcf0 --- /dev/null +++ b/fixtures/focused.spec.php @@ -0,0 +1,66 @@ +context->addSuite("desc", function() {}, true); assert($suite->getPending(), "suite should be pending"); }); - it("should ignore pending if value is null", function() { + it("should ignore pending if pending value is null", function() { $suite = $this->context->addSuite("desc", function() {}); assert(is_null($suite->getPending()), "pending status should be null"); }); + it("should set focused if focused value is supplied", function() { + $suite = $this->context->addSuite("desc", function() {}, null, true); + assert($suite->isFocused(), "suite should be focused"); + }); + + it("should set focused to false if focused value is not supplied", function() { + $suite = $this->context->addSuite("desc", function() {}); + assert(!$suite->isFocused(), "focused status should be false"); + }); + it("should execute a suites bound definition", function() { $suite = $this->context->addSuite("desc", function() { $this->value = "hello"; @@ -54,12 +64,12 @@ }); describe('->addTest()', function() { - it("should set pending status if value is not null", function() { + it("should set pending status if pending value is not null", function() { $test = $this->context->addTest("is a spec", function() {}, true); assert($test->getPending(), "pending status should be true"); }); - it("should ignore pending status if value is null", function() { + it("should ignore pending status if pending value is null", function() { $test = $this->context->addTest("is a spec", function() {}); assert(is_null($test->getPending()), "pending status should be null"); }); @@ -69,6 +79,16 @@ assert($test->getPending(), "pending status should be true"); }); + it("should set focused status if focused value is supplied", function() { + $test = $this->context->addTest("is a spec", function() {}, null, true); + assert($test->isFocused(), "focused status should be true"); + }); + + it("should set focused status if focused value is supplied", function() { + $test = $this->context->addTest("is a spec", function() {}); + assert(!$test->isFocused(), "focused status should be false"); + }); + it('should increase size of root suite', function () { $this->context->addTest('spec', function () { }); $suite = $this->context->getCurrentSuite(); diff --git a/specs/suite.spec.php b/specs/suite.spec.php index ca2bf4a..a666a97 100644 --- a/specs/suite.spec.php +++ b/specs/suite.spec.php @@ -12,6 +12,13 @@ $this->eventEmitter = new EventEmitter(); }); + context("when constructed with default parameters", function() { + it("it should default to an unfocused state", function() { + $suite = new Suite("Suite", function() {}); + assert(!$suite->isFocused(), "suite should not be focused if focused value is not supplied"); + }); + }); + describe('->run()', function() { it("should run multiple tests", function () { $suite = new Suite("Suite", function() {}); @@ -138,6 +145,23 @@ assert($result->getTestCount() == 2, "test count should be 2"); }); + + context('when there are focused tests', function() { + it("should run only the focused tests", function () { + $suite = new Suite("Suite", function() {}); + $suite->addTest(new ItWasRun("should pass", function () {}, true)); + $suite->addTest(new ItWasRun('should fail', function () { + throw new \Exception('woooooo!'); + }, true)); + $suite->addTest(new ItWasRun("should not be run", function () {})); + $suite->addTest(new ItWasRun("should also not be run", function () {})); + + $result = new TestResult($this->eventEmitter); + $suite->setEventEmitter($this->eventEmitter); + $suite->run($result); + assert('2 run, 1 failed' == $result->getSummary(), "result summary should show 2/1"); + }); + }); }); describe("->addTest()", function() { @@ -197,6 +221,46 @@ assert($this->arg === 1, 'should have set definition arguments'); }); }); + + describe('->isFocused()', function() { + context('when explicitly marked as focused', function () { + beforeEach(function() { + $this->suite = new Suite("test suite", function() {}, true); + }); + + it('should return true even if nested tests are not focused', function() { + $test = new Test("test", function() {}); + $this->suite->addTest($test); + assert($this->suite->isFocused(), "suite should be focused"); + }); + }); + + context('when not explicitly marked as focused', function () { + beforeEach(function() { + $this->suite = new Suite("test suite", function() {}); + }); + + it('should return false if nested tests are not focused', function() { + $test = new Test("test", function() {}); + $this->suite->addTest($test); + assert(!$this->suite->isFocused(), "suite should not be focused"); + }); + + it('should return true if nested tests are focused', function() { + $test = new Test("test", function() {}, true); + $this->suite->addTest($test); + assert($this->suite->isFocused(), "suite should be focused"); + }); + + it('should return true if nested suites are focused', function() { + $suite = new Suite("nested suite", function() {}); + $test = new Test("test", function() {}, true); + $suite->addTest($test); + $this->suite->addTest($suite); + assert($this->suite->isFocused(), "suite should be focused"); + }); + }); + }); }); class SuiteScope extends Scope diff --git a/specs/suiteloader.spec.php b/specs/suiteloader.spec.php index 45ca6fe..e569170 100644 --- a/specs/suiteloader.spec.php +++ b/specs/suiteloader.spec.php @@ -18,7 +18,7 @@ describe('->getTests()', function() { it("should return file paths matching *.spec.php recursively", function() { $tests = $this->loader->getTests($this->fixtures); - assert(count($tests) == 4, "suite loader should have loaded 4 specs"); + assert(count($tests) == 5, "suite loader should have loaded 5 specs"); }); it("should return single file if it exists", function() { diff --git a/specs/test.spec.php b/specs/test.spec.php index d944f1d..ed6648b 100644 --- a/specs/test.spec.php +++ b/specs/test.spec.php @@ -8,11 +8,16 @@ describe("Test", function() { - context("when constructed with null definition", function() { + context("when constructed with default parameters", function() { it("it should default to a pending state", function() { $test = new Test("it should be pending"); assert($test->getPending(), "test should be pending if definition is null"); }); + + it("it should default to an unfocused state", function() { + $test = new Test("it should not be focused"); + assert(!$test->isFocused(), "test should not be focused if focused value is not supplied"); + }); }); describe('->run()', function() { diff --git a/src/Core/AbstractTest.php b/src/Core/AbstractTest.php index 8944cc7..48168da 100644 --- a/src/Core/AbstractTest.php +++ b/src/Core/AbstractTest.php @@ -47,6 +47,11 @@ abstract class AbstractTest implements TestInterface */ protected $pending = null; + /** + * @var bool + */ + protected $focused; + /** * @var Scope */ @@ -65,11 +70,13 @@ abstract class AbstractTest implements TestInterface /** * @param string $description * @param callable $definition + * @param bool $focused */ - public function __construct($description, callable $definition) + public function __construct($description, callable $definition, $focused = false) { $this->definition = $definition; $this->description = $description; + $this->focused = $focused; $this->scope = new Scope(); } diff --git a/src/Core/Suite.php b/src/Core/Suite.php index b414cd2..38a80ca 100644 --- a/src/Core/Suite.php +++ b/src/Core/Suite.php @@ -22,6 +22,26 @@ class Suite extends AbstractTest */ protected $halted = false; + /** + * {@inheritdoc} + * + * @return bool + */ + public function isFocused() + { + if ($this->focused === true) { + return $this->focused; + } + + foreach ($this->tests as $test) { + if ($test->isFocused()) { + return true; + } + } + + return false; + } + /** * Add a test to the suite * @@ -74,14 +94,14 @@ public function run(TestResult $result) $this->eventEmitter->emit('suite.start', [$this]); $this->eventEmitter->on('suite.halt', [$this, 'halt']); - foreach ($this->tests as $test) { - + foreach ($this->getTestsToRun() as $test) { if ($this->halted) { break; } $this->runTest($test, $result); } + $this->eventEmitter->emit('suite.end', [$this]); } @@ -111,4 +131,18 @@ protected function runTest(TestInterface $test, TestResult $result) $test->setEventEmitter($this->eventEmitter); $test->run($result); } + + /** + * Get the subset of the defined tests that should actually be run. + * + * @return array + */ + protected function getTestsToRun() + { + $tests = array_filter($this->tests, function (TestInterface $test) { + return $test->isFocused(); + }); + + return empty($tests) ? $this->tests : $tests; + } } diff --git a/src/Core/Test.php b/src/Core/Test.php index 489bc37..1fdf8c6 100644 --- a/src/Core/Test.php +++ b/src/Core/Test.php @@ -18,7 +18,7 @@ class Test extends AbstractTest * @param string $description * @param callable $definition */ - public function __construct($description, callable $definition = null) + public function __construct($description, callable $definition = null, $focused = false) { if ($definition === null) { $this->pending = true; @@ -26,7 +26,17 @@ public function __construct($description, callable $definition = null) //noop }; } - parent::__construct($description, $definition); + parent::__construct($description, $definition, $focused); + } + + /** + * {@inheritdoc} + * + * @return bool + */ + public function isFocused() + { + return $this->focused; } /** diff --git a/src/Core/TestInterface.php b/src/Core/TestInterface.php index a4cfd20..e2868a0 100644 --- a/src/Core/TestInterface.php +++ b/src/Core/TestInterface.php @@ -89,6 +89,13 @@ public function getPending(); */ public function setPending($state); + /** + * Return whether or not the test is focused + * + * @return bool + */ + public function isFocused(); + /** * Return scope for this test. Scope contains instance variables * for a spec diff --git a/src/Dsl.php b/src/Dsl.php index 680be2d..fdb523e 100644 --- a/src/Dsl.php +++ b/src/Dsl.php @@ -53,7 +53,7 @@ function xdescribe($description, callable $fn) */ function xcontext($description, callable $fn) { - xdescribe($description, $fn); + Context::getInstance()->addSuite($description, $fn, true); } /** @@ -67,6 +67,39 @@ function xit($description, callable $fn = null) Context::getInstance()->addTest($description, $fn, true); } +/** + * Create a focused suite + * + * @param $description + * @param callable $fn + */ +function fdescribe($description, callable $fn) +{ + Context::getInstance()->addSuite($description, $fn, null, true); +} + +/** + * Create a focused context + * + * @param $description + * @param callable $fn + */ +function fcontext($description, callable $fn) +{ + Context::getInstance()->addSuite($description, $fn, null, true); +} + +/** + * Create a focused spec + * + * @param $description + * @param callable $fn + */ +function fit($description, callable $fn = null) +{ + Context::getInstance()->addTest($description, $fn, null, true); +} + /** * Add a setup function for all specs in the * current suite diff --git a/src/Runner/Context.php b/src/Runner/Context.php index 86811be..3c6fbc1 100644 --- a/src/Runner/Context.php +++ b/src/Runner/Context.php @@ -48,7 +48,7 @@ public function clear() { $this->suites = [new Suite("", function () { //noop - })]; + }, false)]; } /** @@ -84,12 +84,14 @@ public function getCurrentSuite() * Creates a suite and adds it to the current suite. The newly * created suite will become the new "current" suite * - * @param $description + * @param string $description * @param callable $fn + * @param bool|null $pending + * @param bool $focused */ - public function addSuite($description, callable $fn, $pending = null) + public function addSuite($description, callable $fn, $pending = null, $focused = false) { - $suite = $this->createSuite($description, $fn, $pending); + $suite = $this->createSuite($description, $fn, $pending, $focused); $this->getCurrentSuite()->addTest($suite); array_unshift($this->suites, $suite); @@ -102,12 +104,14 @@ public function addSuite($description, callable $fn, $pending = null) /** * Create a test and add it to the current suite * - * @param $description - * @param $fn + * @param string $description + * @param callable $fn + * @param bool|null $pending + * @param bool $focused */ - public function addTest($description, callable $fn = null, $pending = null) + public function addTest($description, callable $fn = null, $pending = null, $focused = false) { - $test = new Test($description, $fn); + $test = new Test($description, $fn, $focused); if ($pending !== null) { $test->setPending($pending); } @@ -155,14 +159,15 @@ public static function getInstance() /** * Create a Suite based on the state of the Context. * - * @param $description + * @param string $description * @param callable $fn - * @param $pending + * @param bool|null $pending + * @param bool $focused * @return Suite */ - private function createSuite($description, callable $fn, $pending) + private function createSuite($description, callable $fn, $pending, $focused) { - $suite = new Suite($description, $fn); + $suite = new Suite($description, $fn, $focused); if ($pending !== null) { $suite->setPending($pending); } diff --git a/src/Test/ItWasRun.php b/src/Test/ItWasRun.php index 131bc88..e3973d1 100644 --- a/src/Test/ItWasRun.php +++ b/src/Test/ItWasRun.php @@ -13,10 +13,11 @@ class ItWasRun extends Test /** * @param string $description * @param callable $definition + * @param bool $focused */ - public function __construct($description, callable $definition) + public function __construct($description, callable $definition, $focused = false) { - parent::__construct($description, $definition); + parent::__construct($description, $definition, $focused); $this->getScope()->wasRun = false; $this->getScope()->log = false; }