diff --git a/src/PendingCalls/DescribeCall.php b/src/PendingCalls/DescribeCall.php index d96b4a3c..0964bc65 100644 --- a/src/PendingCalls/DescribeCall.php +++ b/src/PendingCalls/DescribeCall.php @@ -74,7 +74,7 @@ final class DescribeCall */ public function __call(string $name, array $arguments): self { - if (! $this->currentBeforeEachCall instanceof \Pest\PendingCalls\BeforeEachCall) { + if (! $this->currentBeforeEachCall instanceof BeforeEachCall) { $this->currentBeforeEachCall = new BeforeEachCall(TestSuite::getInstance(), $this->filename); $this->currentBeforeEachCall->describing = array_merge( diff --git a/src/Support/Backtrace.php b/src/Support/Backtrace.php index 652eb442..86957bcb 100644 --- a/src/Support/Backtrace.php +++ b/src/Support/Backtrace.php @@ -23,7 +23,9 @@ final class Backtrace $current = null; foreach (debug_backtrace(self::BACKTRACE_OPTIONS) as $trace) { - assert(array_key_exists(self::FILE, $trace)); + if (array_key_exists(self::FILE, $trace) === false) { + break; + } $traceFile = str_replace(DIRECTORY_SEPARATOR, '/', $trace[self::FILE]); diff --git a/tests/.snapshots/success.txt b/tests/.snapshots/success.txt index d6ae24e0..79f1da47 100644 --- a/tests/.snapshots/success.txt +++ b/tests/.snapshots/success.txt @@ -95,6 +95,48 @@ PASS Tests\Features\Covers\TraitCoverage ✓ it uses the correct PHPUnit attribute for trait + PASS Tests\Features\DatasetMethodChaining + ✓ beforeEach()->with() applies dataset to tests → receives the dataset value with (10) + ✓ beforeEach()->with() applies dataset to tests → it also receives the dataset value in it() with (10) + ✓ beforeEach()->with() with multiple dataset values → receives each value from the dataset with (1) + ✓ beforeEach()->with() with multiple dataset values → receives each value from the dataset with (2) + ✓ beforeEach()->with() with multiple dataset values → receives each value from the dataset with (3) + ✓ beforeEach()->with() with keyed dataset → receives keyed dataset values with dataset "first" + ✓ beforeEach()->with() with keyed dataset → receives keyed dataset values with dataset "second" + ✓ beforeEach()->with() with closure dataset → receives values from closure dataset with (100) + ✓ beforeEach()->with() with closure dataset → receives values from closure dataset with (200) + ✓ describe()->with() passes dataset to tests → receives the dataset value with (42) + ✓ describe()->with() passes dataset to tests → it also receives it in it() with (42) + ✓ describe()->with() with multiple values → receives each value with (5) + ✓ describe()->with() with multiple values → receives each value with (10) + ✓ describe()->with() with multiple values → receives each value with (15) + ✓ describe()->with() with keyed dataset → receives keyed values with dataset "alpha" + ✓ describe()->with() with keyed dataset → receives keyed values with dataset "beta" + ✓ describe()->with() with closure dataset → receives closure dataset values with (7) + ✓ describe()->with() with closure dataset → receives closure dataset values with (14) + ✓ outer with dataset → inner without dataset → inherits outer dataset with (1) + ✓ nested describe blocks with datasets at multiple levels → level 1 → receives level 1 dataset with (10) + ✓ nested describe blocks with datasets at multiple levels → level 1 → level 2 → receives datasets from all ancestor levels with (10) / (20) + ✓ deeply nested describe with datasets → a → b → c → receives all ancestor datasets with (1) / (2) / (3) + ✓ beforeEach()->with() combined with test->with() → receives both datasets as cross product with (10) / (1) + ✓ beforeEach()->with() combined with test->with() → receives both datasets as cross product with (10) / (2) + ✓ describe()->with() combined with test->with() → receives both datasets with (5) / (50) + ✓ describe()->with() combined with test->with() → receives both datasets with (5) / (60) + ✓ beforeEach closure and beforeEach()->with() coexist → has both the closure state and dataset with (99) + ✓ beforeEach()->with() does not interfere with closure hooks → closures run in order and dataset is applied with (42) + ✓ first describe with dataset → gets its own dataset with (111) + ✓ second describe with different dataset → gets its own dataset, not the sibling with (222) + ✓ third describe without dataset → has no dataset leaking from siblings + ✓ describe()->with() with beforeEach closure → both hook and dataset work with (77) + ✓ describe()->with() with afterEach closure → dataset is available and afterEach runs with (88) + ✓ multiple tests share the same beforeEach dataset → first test gets the dataset with (33) + ✓ multiple tests share the same beforeEach dataset → second test also gets the dataset with (33) + ✓ multiple tests share the same beforeEach dataset → it third test with it() also gets the dataset with (33) + ✓ outer describe → inner describe with dataset on hook → inherits outer beforeEach and has inner dataset with (55) + ✓ outer describe → outer test is unaffected by inner dataset + ✓ describe()->with() preserves depends → first with (9) + ✓ describe()->with() preserves depends → second with (9) + PASS Tests\Features\DatasetsTests - 1 todo ✓ it throws exception if dataset does not exist ✓ it throws exception if dataset already exist @@ -215,6 +257,20 @@ ✓ it may be used with high order after describe block with dataset "formal" ✓ it may be used with high order after describe block with dataset "informal" ✓ after describe block with named dataset with ('after') + ✓ named parameters match by parameter name with ('Taylor', 'taylor@laravel.com') + ✓ named parameters work with multiple dataset items with ('Taylor', 'taylor@laravel.com') + ✓ named parameters work with multiple dataset items with ('James', 'james@laravel.com') + ✓ named parameters work in different order than closure params with ('a', 'b', 'c') + ✓ named parameters work with named dataset keys with dataset "taylor" + ✓ named parameters work with named dataset keys with dataset "james" + ✓ named parameters work with closures that should be resolved with (Closure Object (), Closure Object ()) + ✓ named parameters work with closure type hints with ('Taylor', Closure Object ()) + ✓ named parameters work with registered datasets with ('Taylor', 'taylor@laravel.com') + ✓ named parameters work with registered datasets with ('James', 'james@laravel.com') + ✓ named parameters work with bound closure returning associative array with (Closure Object ()) + ✓ dataset items can mix named and sequential styles with ('Taylor', 'taylor@laravel.com') + ✓ dataset items can mix named and sequential styles with ('James', 'james@laravel.com') #1 + ✓ dataset items can mix named and sequential styles with ('James', 'james@laravel.com') #2 PASS Tests\Features\Depends ✓ first @@ -1813,4 +1869,4 @@ ✓ pass with dataset with ('my-datas-set-value') ✓ within describe → pass with dataset with ('my-datas-set-value') - Tests: 2 deprecated, 4 warnings, 5 incomplete, 2 notices, 39 todos, 35 skipped, 1211 passed (2847 assertions) \ No newline at end of file + Tests: 2 deprecated, 4 warnings, 5 incomplete, 2 notices, 39 todos, 35 skipped, 1265 passed (2925 assertions) \ No newline at end of file diff --git a/tests/Features/DatasetMethodChaining.php b/tests/Features/DatasetMethodChaining.php new file mode 100644 index 00000000..d1474f46 --- /dev/null +++ b/tests/Features/DatasetMethodChaining.php @@ -0,0 +1,287 @@ +with(), describe()->with(), + * and nested describe blocks with datasets. + */ + +// --------------------------------------------------------------- +// beforeEach()->with() inside describe blocks +// --------------------------------------------------------------- + +describe('beforeEach()->with() applies dataset to tests', function () { + beforeEach()->with([10]); + + test('receives the dataset value', function ($value) { + expect($value)->toBe(10); + }); + + it('also receives the dataset value in it()', function ($value) { + expect($value)->toBe(10); + }); +}); + +describe('beforeEach()->with() with multiple dataset values', function () { + beforeEach()->with([1, 2, 3]); + + test('receives each value from the dataset', function ($value) { + expect($value)->toBeIn([1, 2, 3]); + }); +}); + +describe('beforeEach()->with() with keyed dataset', function () { + beforeEach()->with(['first' => [10], 'second' => [20]]); + + test('receives keyed dataset values', function ($value) { + expect($value)->toBeIn([10, 20]); + }); +}); + +describe('beforeEach()->with() with closure dataset', function () { + beforeEach()->with(function () { + yield [100]; + yield [200]; + }); + + test('receives values from closure dataset', function ($value) { + expect($value)->toBeIn([100, 200]); + }); +}); + +// --------------------------------------------------------------- +// describe()->with() method chaining +// --------------------------------------------------------------- + +describe('describe()->with() passes dataset to tests', function () { + test('receives the dataset value', function ($value) { + expect($value)->toBe(42); + }); + + it('also receives it in it()', function ($value) { + expect($value)->toBe(42); + }); +})->with([42]); + +describe('describe()->with() with multiple values', function () { + test('receives each value', function ($value) { + expect($value)->toBeIn([5, 10, 15]); + }); +})->with([5, 10, 15]); + +describe('describe()->with() with keyed dataset', function () { + test('receives keyed values', function ($value) { + expect($value)->toBeIn([100, 200]); + }); +})->with(['alpha' => [100], 'beta' => [200]]); + +describe('describe()->with() with closure dataset', function () { + test('receives closure dataset values', function ($value) { + expect($value)->toBeIn([7, 14]); + }); +})->with(function () { + yield [7]; + yield [14]; +}); + +// --------------------------------------------------------------- +// Nested describe blocks with datasets +// --------------------------------------------------------------- + +describe('outer with dataset', function () { + describe('inner without dataset', function () { + test('inherits outer dataset', function (...$args) { + expect($args)->toBe([1]); + }); + }); +})->with([1]); + +describe('nested describe blocks with datasets at multiple levels', function () { + describe('level 1', function () { + test('receives level 1 dataset', function (...$args) { + expect($args)->toBe([10]); + }); + + describe('level 2', function () { + test('receives datasets from all ancestor levels', function (...$args) { + expect($args)->toBe([10, 20]); + }); + })->with([20]); + })->with([10]); +}); + +describe('deeply nested describe with datasets', function () { + describe('a', function () { + describe('b', function () { + describe('c', function () { + test('receives all ancestor datasets', function (...$args) { + expect($args)->toBe([1, 2, 3]); + }); + })->with([3]); + })->with([2]); + })->with([1]); +}); + +// --------------------------------------------------------------- +// Combining hook datasets with test-level datasets +// --------------------------------------------------------------- + +describe('beforeEach()->with() combined with test->with()', function () { + beforeEach()->with([10]); + + test('receives both datasets as cross product', function ($hookValue, $testValue) { + expect($hookValue)->toBe(10); + expect($testValue)->toBeIn([1, 2]); + })->with([1, 2]); +}); + +describe('describe()->with() combined with test->with()', function () { + test('receives both datasets', function ($describeValue, $testValue) { + expect($describeValue)->toBe(5); + expect($testValue)->toBeIn([50, 60]); + })->with([50, 60]); +})->with([5]); + +// --------------------------------------------------------------- +// beforeEach()->with() combined with beforeEach closure +// --------------------------------------------------------------- + +describe('beforeEach closure and beforeEach()->with() coexist', function () { + beforeEach(function () { + $this->setupValue = 'initialized'; + }); + + beforeEach()->with([99]); + + test('has both the closure state and dataset', function ($value) { + expect($this->setupValue)->toBe('initialized'); + expect($value)->toBe(99); + }); +}); + +describe('beforeEach()->with() does not interfere with closure hooks', function () { + beforeEach(function () { + $this->counter = 1; + }); + + beforeEach(function () { + $this->counter++; + }); + + beforeEach()->with([42]); + + test('closures run in order and dataset is applied', function ($value) { + expect($this->counter)->toBe(2); + expect($value)->toBe(42); + }); +}); + +// --------------------------------------------------------------- +// Dataset isolation between describe blocks +// --------------------------------------------------------------- + +describe('first describe with dataset', function () { + beforeEach()->with([111]); + + test('gets its own dataset', function ($value) { + expect($value)->toBe(111); + }); +}); + +describe('second describe with different dataset', function () { + beforeEach()->with([222]); + + test('gets its own dataset, not the sibling', function ($value) { + expect($value)->toBe(222); + }); +}); + +describe('third describe without dataset', function () { + test('has no dataset leaking from siblings', function () { + expect(true)->toBeTrue(); + }); +}); + +// --------------------------------------------------------------- +// describe()->with() combined with beforeEach hooks +// --------------------------------------------------------------- + +describe('describe()->with() with beforeEach closure', function () { + beforeEach(function () { + $this->hookRan = true; + }); + + test('both hook and dataset work', function ($value) { + expect($this->hookRan)->toBeTrue(); + expect($value)->toBe(77); + }); +})->with([77]); + +describe('describe()->with() with afterEach closure', function () { + afterEach(function () { + expect($this->value)->toBe(88); + }); + + test('dataset is available and afterEach runs', function ($value) { + $this->value = $value; + expect($value)->toBe(88); + }); +})->with([88]); + +// --------------------------------------------------------------- +// Multiple tests in a describe with beforeEach()->with() +// --------------------------------------------------------------- + +describe('multiple tests share the same beforeEach dataset', function () { + beforeEach()->with([33]); + + test('first test gets the dataset', function ($value) { + expect($value)->toBe(33); + }); + + test('second test also gets the dataset', function ($value) { + expect($value)->toBe(33); + }); + + it('third test with it() also gets the dataset', function ($value) { + expect($value)->toBe(33); + }); +}); + +// --------------------------------------------------------------- +// Nested describe with beforeEach()->with() at inner level +// --------------------------------------------------------------- + +describe('outer describe', function () { + beforeEach(function () { + $this->outer = true; + }); + + describe('inner describe with dataset on hook', function () { + beforeEach()->with([55]); + + test('inherits outer beforeEach and has inner dataset', function ($value) { + expect($this->outer)->toBeTrue(); + expect($value)->toBe(55); + }); + }); + + test('outer test is unaffected by inner dataset', function () { + expect($this->outer)->toBeTrue(); + }); +}); + +// --------------------------------------------------------------- +// describe()->with() with depends +// --------------------------------------------------------------- + +describe('describe()->with() preserves depends', function () { + test('first', function ($value) { + expect($value)->toBe(9); + }); + + test('second', function ($value) { + expect($value)->toBe(9); + })->depends('first'); +})->with([9]); diff --git a/tests/Visual/Parallel.php b/tests/Visual/Parallel.php index 68b3ee1f..ab62146d 100644 --- a/tests/Visual/Parallel.php +++ b/tests/Visual/Parallel.php @@ -16,7 +16,7 @@ $run = function () { test('parallel', function () use ($run) { expect($run('--exclude-group=integration')) - ->toContain('Tests: 2 deprecated, 4 warnings, 5 incomplete, 3 notices, 39 todos, 26 skipped, 1196 passed (2809 assertions)') + ->toContain('Tests: 2 deprecated, 4 warnings, 5 incomplete, 3 notices, 39 todos, 26 skipped, 1250 passed (2887 assertions)') ->toContain('Parallel: 3 processes'); })->skipOnWindows();