├── docs ├── _config.yml ├── resources.md ├── index.md ├── lenses.md ├── filters.md ├── fields.md └── actions.md ├── src ├── Fields │ ├── FieldNotFoundException.php │ ├── FieldHelper.php │ └── MockFieldElement.php ├── Actions │ ├── ActionNotFoundException.php │ ├── InvalidNovaActionException.php │ ├── NovaActionTest.php │ ├── MockAction.php │ ├── MockActionElement.php │ └── MockActionResponse.php ├── Exceptions │ └── InvalidModelException.php ├── MockComponent.php ├── Lenses │ ├── InvalidNovaLensException.php │ ├── NovaLensTest.php │ ├── MockLensRequest.php │ ├── MockLensQuery.php │ └── MockLens.php ├── Filters │ ├── InvalidNovaFilterException.php │ ├── NovaFilterTest.php │ ├── MockFilterQuery.php │ └── MockFilter.php ├── Resources │ ├── InvalidNovaResourceException.php │ ├── MockResource.php │ └── NovaResourceTest.php ├── Constraints │ ├── ArrayHasInstanceOf.php │ ├── EloquentCollectionContains.php │ ├── HasField.php │ ├── IsActionResponseType.php │ └── HasValidFields.php └── Traits │ ├── FilterAssertions.php │ ├── ActionAssertions.php │ └── FieldAssertions.php ├── .github ├── FUNDING.yml └── workflows │ └── ci.yml ├── LICENSE.md ├── CHANGELOG.md ├── composer.json ├── README.md ├── CONTRIBUTING.md └── .php-cs-fixer.dist.php /docs/_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-slate -------------------------------------------------------------------------------- /src/Fields/FieldNotFoundException.php: -------------------------------------------------------------------------------- 1 | component = $component; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/Lenses/InvalidNovaLensException.php: -------------------------------------------------------------------------------- 1 | novaResource(UserResource::class); 17 | } 18 | } 19 | ``` 20 | 21 | The following assertions can be run on the Nova Resource: 22 | 23 | * [Action Tests](actions.md#testing-actions-on-components) 24 | * [Field Tests](fields.md#testing-fields-on-components) 25 | * [Filter Tests](filters.md#testing-filters-on-components) 26 | -------------------------------------------------------------------------------- /src/Filters/NovaFilterTest.php: -------------------------------------------------------------------------------- 1 | class = $class; 14 | } 15 | 16 | /** 17 | * {@inheritdoc} 18 | */ 19 | public function toString(): string 20 | { 21 | return \sprintf('contains instance of "%s"', $this->class); 22 | } 23 | 24 | public function matches($other): bool 25 | { 26 | foreach ($other as $field) { 27 | if ($field instanceof $this->class) { 28 | return true; 29 | } 30 | } 31 | 32 | return false; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Lenses/NovaLensTest.php: -------------------------------------------------------------------------------- 1 | object = $object; 19 | } 20 | 21 | /** 22 | * {@inheritdoc} 23 | */ 24 | public function toString(): string 25 | { 26 | return \sprintf('contains the given %s', (new ReflectionClass($this->object))->getShortName()); 27 | } 28 | 29 | public function matches($collection): bool 30 | { 31 | return $collection->find($this->object->getKey()) instanceof Model; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/Constraints/HasField.php: -------------------------------------------------------------------------------- 1 | fieldName = $fieldName; 17 | $this->allowPanels = $allowPanels; 18 | } 19 | 20 | /** 21 | * {@inheritdoc} 22 | */ 23 | public function toString(): string 24 | { 25 | return \sprintf('contains field "%s"', $this->fieldName); 26 | } 27 | 28 | public function matches($other): bool 29 | { 30 | return FieldHelper::findField($other, $this->fieldName, $this->allowPanels) instanceof Field; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/Lenses/MockLensRequest.php: -------------------------------------------------------------------------------- 1 | withFilters = false; 17 | $this->withOrdering = false; 18 | } 19 | 20 | public function withFilters($query) 21 | { 22 | $this->withFilters = true; 23 | 24 | return $query; 25 | } 26 | 27 | public function withOrdering($query) 28 | { 29 | $this->withOrdering = true; 30 | 31 | return $query; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/Constraints/IsActionResponseType.php: -------------------------------------------------------------------------------- 1 | actionType = $actionType; 15 | } 16 | 17 | /** 18 | * {@inheritdoc} 19 | */ 20 | public function toString(): string 21 | { 22 | return \sprintf('matches the "%s" Action response', $this->actionType); 23 | } 24 | 25 | public function matches($response): bool 26 | { 27 | $structure = \array_keys(\call_user_func([Action::class, $this->actionType], 'param 1', 'param 2')); 28 | $responseKeys = \array_keys($response); 29 | 30 | \sort($structure); 31 | \sort($responseKeys); 32 | 33 | return $structure === $responseKeys; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/Resources/NovaResourceTest.php: -------------------------------------------------------------------------------- 1 | allowPanels = $allowPanels; 16 | } 17 | 18 | /** 19 | * {@inheritdoc} 20 | */ 21 | public function toString(): string 22 | { 23 | return 'contains valid fields only'; 24 | } 25 | 26 | public function matches($other): bool 27 | { 28 | foreach ($other as $field) { 29 | if ($this->allowPanels && $field instanceof Panel) { 30 | if (! $this->matches($field->data)) { 31 | return false; 32 | } 33 | } elseif (! $field instanceof Field && ! $field instanceof \Laravel\Nova\Fields\FieldElement) { 34 | return false; 35 | } 36 | } 37 | 38 | return true; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Josh Gaber 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /src/Actions/MockAction.php: -------------------------------------------------------------------------------- 1 | component->handle( 27 | new ActionFields(collect($fields), collect()), 28 | $models instanceof Collection ? $models : collect(Arr::wrap($models)) 29 | ) 30 | ); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to NovaUnit will be documented in this file. 4 | 5 | ## 3.1 6 | 7 | - Compatibility with PHP 8, Laravel 10 and PHPUnit 10 8 | 9 | ## 3.0.2 10 | 11 | - Allow `FieldElement` as a valid field instance 12 | 13 | ## 3.0.1 14 | 15 | - Use built-in `NovaRequest` for mocking components 16 | 17 | ## 3.0 18 | 19 | - Compatibility with Laravel Nova 4 20 | 21 | ## 2.3 22 | 23 | - Compatibility with Laravel 9 24 | 25 | ## 2.2 26 | 27 | - Support for PHP 8 28 | 29 | ## 2.1 30 | 31 | - Added assertion method for checking creation and update rules 32 | - **bugfix**: Report invalid action response as an assertion failure 33 | 34 | ## 2.0 35 | 36 | - Compatibility with Laravel 8 37 | 38 | ## 1.1 - 2020-06-22 39 | 40 | - Added assertions on Lens query results have been moved to their own class 41 | - Added assertions on the configuration of fields added to Actions, Lenses and Resources 42 | - Added assertions on the configuration of actions added to Lenses and Resources 43 | - Added assertions on Nova Filters 44 | - Added assertions on filter sets on Lenses and Resources 45 | - Minor bug fixes and refactor 46 | 47 | ## 1.0.2 - 2020-06-17 48 | 49 | - Allow field assertions to read fields within panels on Resources and Lenses 50 | - **bugfix**: prevent action assertions from accessing fields 51 | 52 | ## 1.0.1 - 2020-06-15 53 | 54 | - **bugfix**: allow users to pass single model instances to Nova Action handle 55 | 56 | ## 1.0 - 2020-06-01 57 | 58 | - initial release, with tests for Actions, Lenses and Resources. 59 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | NovaUnit is a unit-testing package for Laravel Nova, built using PHPUnit. NovaUnit provides you with assertions for Nova Actions, Filters, Lenses and Resources, so you can create great administration panels with confidence. 2 | 3 | ## Installation 4 | 5 | You can install this package into your Laravel project via composer: 6 | 7 | ```sh 8 | composer require --dev joshgaber/novaunit 9 | ``` 10 | 11 | ### Requirements 12 | 13 | * PHP 7.2 or higher 14 | * [Laravel](https://laravel.com/) 6.x - 10.x 15 | * [Laravel Nova](https://nova.laravel.com/) 2.x - 4.x 16 | * [PHPUnit](https://github.com/sebastianbergmann/phpunit) 8.x - 10.x 17 | 18 | ## Usage 19 | 20 | NovaUnit can be used to perform assertions on Actions, Filters, Lenses and Resources. 21 | 22 | To access the test classes, import and use the base test traits: 23 | 24 | ```php 25 | class ClearLogsTest extends TestCase { 26 | use NovaActionTest; 27 | } 28 | ``` 29 | 30 | Currently, there are four traits: `NovaActionTest`, `NovaFilterTest`, `NovaLensTest` and `NovaResourceTest`. To test these components, invoke the respective test class (ie. `novaAction` for Actions). 31 | 32 | Once you've created the mock with the initial test class, you can begin testing different aspect of the component: 33 | 34 | ```php 35 | $this->novaAction(ClearLogs::class) 36 | ->assertHasField('since_date'); 37 | ``` 38 | 39 | ## Available Methods 40 | 41 | * [Action Methods](actions.md) 42 | * [Field Methods](fields.md) 43 | * [Filter Methods](filters.md) 44 | * [Lens Methods](lenses.md) 45 | * [Resource Methods](resources.md) 46 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "joshgaber/novaunit", 3 | "description": "Unit testing suite for Laravel Nova, built to extend PHPUnit", 4 | "version": "3.1.0", 5 | "keywords": [ 6 | "laravel", 7 | "nova", 8 | "joshgaber", 9 | "novaunit" 10 | ], 11 | "homepage": "https://github.com/joshgaber/novaunit", 12 | "license": "MIT", 13 | "type": "library", 14 | "authors": [ 15 | { 16 | "name": "Josh Gaber", 17 | "email": "joshgaber@gmail.com", 18 | "homepage": "https://joshgaber.ca", 19 | "role": "Developer" 20 | } 21 | ], 22 | "require": { 23 | "php": "^8.0", 24 | "ext-mbstring": "*", 25 | "cakephp/chronos": ">=2.0.0", 26 | "illuminate/support": "^8.83.4|^9.3.1|^10.0", 27 | "laravel/nova": "^4.0", 28 | "phpunit/phpunit": "^9.0|^10.0" 29 | }, 30 | "require-dev": { 31 | "friendsofphp/php-cs-fixer": "^3.0", 32 | "orchestra/testbench": "^6.0|^8.0" 33 | }, 34 | "repositories": [ 35 | { 36 | "type": "composer", 37 | "url": "https://nova.laravel.com" 38 | } 39 | ], 40 | "autoload": { 41 | "psr-4": { 42 | "JoshGaber\\NovaUnit\\": "src" 43 | } 44 | }, 45 | "autoload-dev": { 46 | "psr-4": { 47 | "JoshGaber\\NovaUnit\\Tests\\": "tests" 48 | } 49 | }, 50 | "scripts": { 51 | "test": "vendor/bin/phpunit", 52 | "test-coverage": "vendor/bin/phpunit --coverage-html coverage", 53 | "lint": "vendor/bin/php-cs-fixer fix" 54 | }, 55 | "config": { 56 | "sort-packages": true 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/Fields/FieldHelper.php: -------------------------------------------------------------------------------- 1 | data, $fieldName, $allowPanels); 23 | if ($panelField instanceof Field) { 24 | return $panelField; 25 | } 26 | } elseif (self::fieldMatches($field, $fieldName)) { 27 | return $field; 28 | } 29 | } 30 | 31 | return null; 32 | } 33 | 34 | /** 35 | * Determines whether the given field has a name or attribute matching the provided field name. 36 | * 37 | * @param mixed $field The field 38 | * @param string $fieldName the field name or attribute to match 39 | * @return bool 40 | */ 41 | public static function fieldMatches($field, string $fieldName): bool 42 | { 43 | return $field instanceof Field && ( 44 | $field->attribute === $fieldName || 45 | \mb_strtolower($field->name) === \mb_strtolower($fieldName) 46 | ); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Linting and Tests 2 | 3 | on: 4 | pull_request: 5 | branches: [main, release/*] 6 | paths-ignore: 7 | - "**/*.md" 8 | - "*.md" 9 | - "**/*.json" 10 | push: 11 | branches: [main] 12 | 13 | jobs: 14 | laravel-tests: 15 | runs-on: ubuntu-latest 16 | 17 | strategy: 18 | matrix: 19 | php: ['8.0', '8.1', '8.2'] 20 | 21 | steps: 22 | - uses: actions/checkout@v2 23 | - uses: shivammathur/setup-php@v2 24 | with: 25 | php-version: ${{ matrix.php }} 26 | - name: Copy .env.testing 27 | run: php -r "file_exists('.env.testing') || copy('.env.testing.example', '.env.testing');" 28 | - name: Set Nova credentials 29 | run: echo '${{ secrets.NOVA_4_CREDENTIALS }}' > auth.json 30 | - name: Install Dependencies 31 | run: composer update --no-interaction --prefer-source 32 | - name: Execute tests (Unit and Feature tests) via PHPUnit 33 | run: ./vendor/bin/phpunit 34 | lint: 35 | runs-on: ubuntu-latest 36 | 37 | steps: 38 | - uses: actions/checkout@v2 39 | - uses: shivammathur/setup-php@v2 40 | with: 41 | php-version: "8.1" 42 | - name: Copy .env.testing 43 | run: php -r "file_exists('.env.testing') || copy('.env.testing.example', '.env.testing');" 44 | - name: Set Nova credentials 45 | run: echo '${{ secrets.NOVA_4_CREDENTIALS }}' > auth.json 46 | - name: Install Dependencies 47 | run: composer update --no-interaction --prefer-source 48 | - name: Execute tests (Unit and Feature tests) via PHPUnit 49 | run: ./vendor/bin/php-cs-fixer fix --dry-run 50 | -------------------------------------------------------------------------------- /docs/lenses.md: -------------------------------------------------------------------------------- 1 | # Lenses 2 | 3 | * [Testing Lenses](#testing-lenses) 4 | 5 | ## Testing Lenses 6 | 7 | To create the testing object for a Nova Lens, add the test trait to your class, and invoke the test method. 8 | 9 | ```php 10 | class TestClass extends TestCase 11 | { 12 | use NovaLensTest; 13 | 14 | public function testNovaLens() 15 | { 16 | $lens = $this->novaLens(MyLens::class); 17 | } 18 | } 19 | ``` 20 | 21 | The following assertions can be run on the Nova Lens: 22 | 23 | * [Action Tests](actions.md#testing-actions-on-components) 24 | * [Field Tests](fields.md#testing-fields-on-components) 25 | * [Filter Tests](filters.md#testing-filters-on-components) 26 | 27 | ## Testing Lens Query 28 | 29 | ```php 30 | $response = $lens->query(User::class); 31 | ``` 32 | 33 | Invokes the `query` method on the lens with the given parameters. 34 | 35 | * `$model` - The class path of the model to build the query from 36 | 37 | ### `assertContains($element)` 38 | 39 | ```php 40 | $lens->assertContains(User::find(1)); 41 | ``` 42 | 43 | Assert that `$element` is returned when this lens query is applied 44 | 45 | ### `assertMissing($element)` 46 | 47 | ```php 48 | $lens->assertMissing(User::find(1)); 49 | ``` 50 | 51 | Assert that `$element` is not returned when this lens query is applied 52 | 53 | ### `assertCount($count)` 54 | 55 | ```php 56 | $lens->assertCount(3); 57 | ``` 58 | 59 | Assert that a specific number of records are returned when this lens query is applied 60 | 61 | ### `assertWithFilters()` 62 | 63 | ```php 64 | $lens->assertWithFilters(); 65 | ``` 66 | 67 | Assert that the provided filter values will be applied to this query (ie., the response will be wrapped in `$request->withFilters()`) 68 | 69 | ### `assertWithOrdering()` 70 | 71 | ```php 72 | $lens->assertWithOrdering(); 73 | ``` 74 | 75 | Assert that the provided ordering rules will be applied to this query (ie., the response will be wrapped in `$request->withOrdering()`) 76 | -------------------------------------------------------------------------------- /src/Filters/MockFilterQuery.php: -------------------------------------------------------------------------------- 1 | results = $query->get(); 17 | } 18 | 19 | /** 20 | * Assert that the query builder will return the given model. 21 | * 22 | * @param Model $element The model contained in the query result 23 | * @param string $message 24 | * @return $this 25 | */ 26 | public function assertContains(Model $element, string $message = ''): self 27 | { 28 | PHPUnit::assertThat( 29 | $this->results, 30 | new EloquentCollectionContains($element), 31 | $message 32 | ); 33 | 34 | return $this; 35 | } 36 | 37 | /** 38 | * Assert that the query builder will not return the given model. 39 | * 40 | * @param Model $element The model not contained in the query result 41 | * @param string $message 42 | * @return $this 43 | */ 44 | public function assertMissing(Model $element, string $message = ''): self 45 | { 46 | PHPUnit::assertThat( 47 | $this->results, 48 | PHPUnit::logicalNot(new EloquentCollectionContains($element)), 49 | $message 50 | ); 51 | 52 | return $this; 53 | } 54 | 55 | /** 56 | * Assert that the query builder returns the given number of records. 57 | * 58 | * @param int $count 59 | * @param string $message 60 | * @return $this 61 | */ 62 | public function assertCount(int $count, string $message = ''): self 63 | { 64 | PHPUnit::assertCount($count, $this->results, $message); 65 | 66 | return $this; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/Traits/FilterAssertions.php: -------------------------------------------------------------------------------- 1 | component->filters(NovaRequest::createFromGlobals()), 25 | new ArrayHasInstanceOf($action), 26 | $message 27 | ); 28 | 29 | return $this; 30 | } 31 | 32 | /** 33 | * Asserts that this component does not have the specified field. 34 | * 35 | * @param string $action The class path of the Filter 36 | * @param string $message 37 | * @return $this 38 | */ 39 | public function assertFilterMissing(string $action, string $message = ''): self 40 | { 41 | PHPUnit::assertThat( 42 | $this->component->filters(NovaRequest::createFromGlobals()), 43 | PHPUnit::logicalNot(new ArrayHasInstanceOf($action)), 44 | $message 45 | ); 46 | 47 | return $this; 48 | } 49 | 50 | /** 51 | * Asserts that this component has no Filters specified. 52 | * 53 | * @param string $message 54 | * @return $this 55 | */ 56 | public function assertHasNoFilters(string $message = ''): self 57 | { 58 | PHPUnit::assertCount(0, $this->component->filters(NovaRequest::createFromGlobals()), $message); 59 | 60 | return $this; 61 | } 62 | 63 | /** 64 | * Asserts that all filters on this component are valid Filters. 65 | * 66 | * @param string $message 67 | * @return $this 68 | */ 69 | public function assertHasValidFilters(string $message = ''): self 70 | { 71 | PHPUnit::assertThat( 72 | $this->component->filters(NovaRequest::createFromGlobals()), 73 | PHPUnit::logicalAnd( 74 | new IsType(IsType::TYPE_ARRAY), 75 | new TraversableContainsOnly(Filter::class, false) 76 | ), 77 | $message 78 | ); 79 | 80 | return $this; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/Actions/MockActionElement.php: -------------------------------------------------------------------------------- 1 | action = $action; 15 | } 16 | 17 | /** 18 | * Assert that the action can be shown on the index view. 19 | * 20 | * @param string $message 21 | * @return $this 22 | */ 23 | public function assertShownOnIndex(string $message = ''): self 24 | { 25 | PHPUnit::assertTrue($this->action->showOnIndex, $message); 26 | 27 | return $this; 28 | } 29 | 30 | /** 31 | * Assert that the action is hidden from the index view. 32 | * 33 | * @param string $message 34 | * @return $this 35 | */ 36 | public function assertHiddenFromIndex(string $message = ''): self 37 | { 38 | PHPUnit::assertFalse($this->action->showOnIndex, $message); 39 | 40 | return $this; 41 | } 42 | 43 | /** 44 | * Assert that the action can be shown on the detail view. 45 | * 46 | * @param string $message 47 | * @return $this 48 | */ 49 | public function assertShownOnDetail(string $message = ''): self 50 | { 51 | PHPUnit::assertTrue($this->action->showOnDetail, $message); 52 | 53 | return $this; 54 | } 55 | 56 | /** 57 | * Assert that the action is hidden from the detail view. 58 | * 59 | * @param string $message 60 | * @return $this 61 | */ 62 | public function assertHiddenFromDetail(string $message = ''): self 63 | { 64 | PHPUnit::assertFalse($this->action->showOnDetail, $message); 65 | 66 | return $this; 67 | } 68 | 69 | /** 70 | * Assert that the action can be shown on table rows. 71 | * 72 | * @param string $message 73 | * @return $this 74 | */ 75 | public function assertShownInline(string $message = ''): self 76 | { 77 | PHPUnit::assertTrue($this->action->showInline, $message); 78 | 79 | return $this; 80 | } 81 | 82 | /** 83 | * @deprecated 84 | */ 85 | public function assertShownOnTableRow(string $message = ''): self 86 | { 87 | return $this->assertShownInline($message); 88 | } 89 | 90 | /** 91 | * Assert that the action is hidden from table rows. 92 | * 93 | * @param string $message 94 | * @return $this 95 | */ 96 | public function assertNotShownInline(string $message = ''): self 97 | { 98 | PHPUnit::assertFalse($this->action->showInline, $message); 99 | 100 | return $this; 101 | } 102 | 103 | /** 104 | * @deprecated 105 | */ 106 | public function assertHiddenFromTableRow(string $message = ''): self 107 | { 108 | return $this->assertNotShownInline($message); 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /src/Lenses/MockLensQuery.php: -------------------------------------------------------------------------------- 1 | query = $query; 19 | $this->results = $query->get(); 20 | $this->request = $request; 21 | } 22 | 23 | /** 24 | * Assert that the query builder will return the given model. 25 | * 26 | * @param Model $element The model contained in the query result 27 | * @param string $message 28 | * @return $this 29 | */ 30 | public function assertContains(Model $element, string $message = ''): self 31 | { 32 | PHPUnit::assertThat( 33 | $this->results, 34 | new EloquentCollectionContains($element), 35 | $message 36 | ); 37 | 38 | return $this; 39 | } 40 | 41 | /** 42 | * Assert that the query builder will not return the given model. 43 | * 44 | * @param Model $element The model not contained in the query result 45 | * @param string $message 46 | * @return $this 47 | */ 48 | public function assertMissing(Model $element, string $message = ''): self 49 | { 50 | PHPUnit::assertThat( 51 | $this->results, 52 | PHPUnit::logicalNot(new EloquentCollectionContains($element)), 53 | $message 54 | ); 55 | 56 | return $this; 57 | } 58 | 59 | /** 60 | * Assert that the query builder returns the given number of records. 61 | * 62 | * @param int $count 63 | * @param string $message 64 | * @return $this 65 | */ 66 | public function assertCount(int $count, string $message = ''): self 67 | { 68 | PHPUnit::assertCount( 69 | $count, 70 | $this->results, 71 | $message 72 | ); 73 | 74 | return $this; 75 | } 76 | 77 | /** 78 | * Assert that the query builder uses the "withFilters" request. 79 | * 80 | * @param string $message 81 | * @return $this 82 | */ 83 | public function assertWithFilters(string $message = ''): self 84 | { 85 | PHPUnit::assertTrue($this->request->withFilters, $message); 86 | 87 | return $this; 88 | } 89 | 90 | /** 91 | * Assert that the query builder uses the "withOrdering" request. 92 | * 93 | * @param string $message 94 | * @return $this 95 | */ 96 | public function assertWithOrdering(string $message = ''): self 97 | { 98 | PHPUnit::assertTrue($this->request->withOrdering, $message); 99 | 100 | return $this; 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | > [!WARNING] 2 | > Thank you to everyone who has supported, downloaded and contributed to NovaUnit over the years. **This repository is no longer maintained**, and has been moved to https://github.com/qvelocity/novaunit. Big shout out to @robertmarney and the team at @qvelocity for continuing this project! 3 | 4 | # NovaUnit 5 | 6 | [![Latest Version on Packagist](https://img.shields.io/packagist/v/joshgaber/novaunit.svg?style=flat-square)](https://packagist.org/packages/joshgaber/novaunit) 7 | [![Code Coverage](https://scrutinizer-ci.com/g/joshgaber/novaunit/badges/coverage.png)](https://scrutinizer-ci.com/g/joshgaber/novaunit/) 8 | [![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/joshgaber/novaunit/badges/quality-score.png)](https://scrutinizer-ci.com/g/joshgaber/novaunit/) 9 | [![Total Downloads](https://img.shields.io/packagist/dt/joshgaber/novaunit.svg?style=flat-square)](https://packagist.org/packages/joshgaber/novaunit) 10 | 11 | [NovaUnit](https://joshgaber.github.io/NovaUnit) is a unit-testing package for Laravel Nova, built using PHPUnit. NovaUnit provides you with assertions for Nova Actions, Lenses and Resources, so you can create great administration panels with confidence. 12 | 13 | ## Installation 14 | 15 | You can install the package in your Laravel Project via composer: 16 | 17 | ```sh 18 | composer require --dev joshgaber/novaunit 19 | ``` 20 | 21 | ### Requirements 22 | 23 | * PHP 7.3 or higher 24 | * [Laravel](https://laravel.com/) 6.x - 10.x 25 | * [Laravel Nova](https://nova.laravel.com/) 2.x - 4.x 26 | * [PHPUnit](https://github.com/sebastianbergmann/phpunit) 8.5.x - 10.x 27 | 28 | ## Usage 29 | 30 | To access the test classes, import and use the base test traits: 31 | 32 | ```php 33 | class ClearLogsTest extends TestCase { 34 | use NovaActionTest; 35 | } 36 | ``` 37 | 38 | Once you've created the mock with the initial test class, you can begin testing different aspect of the component: 39 | 40 | ```php 41 | $this->novaAction(ClearLogs::class) 42 | ->assertHasField('since_date'); 43 | ``` 44 | 45 | For a list of available methods, see the [full docs site](https://joshgaber.github.io/NovaUnit). 46 | 47 | ### Changelog 48 | 49 | Please see [CHANGELOG](CHANGELOG.md) for more information what has changed recently. 50 | 51 | ## Contributing 52 | 53 | Please see [CONTRIBUTING](CONTRIBUTING.md) for details. 54 | 55 | ### Security 56 | 57 | If you discover any security related issues, please email joshgaber@gmail.com instead of using the issue tracker. 58 | 59 | ## Created By 60 | 61 | * [Josh Gaber](https://github.com/joshgaber) (Creator & Maintainer) 62 | 63 | ## Contributors 64 | 65 | * [Joshua Lauwrich Nandy](https://github.com/joshua060198) 66 | * [nicko170](https://github.com/nicko170) 67 | * [Henry Ávila](https://github.com/henryavila) 68 | * [Peter Elmered](https://github.com/pelmered) 69 | 70 | ## License 71 | 72 | The MIT License (MIT). Please see [License File](LICENSE.md) for more information. 73 | 74 | ## Laravel Package Boilerplate 75 | 76 | This package was generated using the [Laravel Package Boilerplate](https://laravelpackageboilerplate.com). 77 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Contributions are **welcome** and will be fully **credited**. 4 | 5 | Please read and understand the contribution guide before creating an issue or pull request. 6 | 7 | ## Etiquette 8 | 9 | This project is open source, and as such, the maintainers give their free time to build and maintain the source code 10 | held within. They make the code freely available in the hope that it will be of use to other developers. It would be 11 | extremely unfair for them to suffer abuse or anger for their hard work. 12 | 13 | Please be considerate towards maintainers when raising issues or presenting pull requests. Let's show the 14 | world that developers are civilized and selfless people. 15 | 16 | It's the duty of the maintainer to ensure that all submissions to the project are of sufficient 17 | quality to benefit the project. Many developers have different skillsets, strengths, and weaknesses. Respect the maintainer's decision, and do not be upset or abusive if your submission is not used. 18 | 19 | ## Viability 20 | 21 | When requesting or submitting new features, first consider whether it might be useful to others. Open 22 | source projects are used by many developers, who may have entirely different needs to your own. Think about 23 | whether or not your feature is likely to be used by other users of the project. 24 | 25 | ## Procedure 26 | 27 | Before filing an issue: 28 | 29 | - Attempt to replicate the problem, to ensure that it wasn't a coincidental incident. 30 | - Check to make sure your feature suggestion isn't already present within the project. 31 | - Check the pull requests tab to ensure that the bug doesn't have a fix in progress. 32 | - Check the pull requests tab to ensure that the feature isn't already in progress. 33 | 34 | Before submitting a pull request: 35 | 36 | - Check the codebase to ensure that your feature doesn't already exist. 37 | - Check the pull requests to ensure that another person hasn't already submitted the feature or fix. 38 | 39 | ## Requirements 40 | 41 | If the project maintainer has any additional requirements, you will find them listed here. 42 | 43 | - **[PSR-12 Coding Standard](https://www.php-fig.org/psr/psr-12/)** - The easiest way to apply the conventions is to run `composer lint` from the root folder. Your code cannot be merged if it does not conform to standards. 44 | 45 | - **Add tests!** - Because this is a unit testing package, your patch must have 100% code coverage, including both affirmative and negative cases. 46 | 47 | - **Document any change in behaviour** - Make sure the `README.md` and any other relevant documentation are kept up-to-date. 48 | 49 | - **Consider our release cycle** - We try to follow [SemVer v2.0.0](https://semver.org/). Randomly breaking public APIs is not an option. 50 | 51 | - **One pull request, one feature** - If you want to do more than one thing, send multiple pull requests. 52 | 53 | - **Send coherent history** - Make sure each individual commit in your pull request is meaningful. If you had to make multiple intermediate commits while developing, please [squash them](https://www.git-scm.com/book/en/v2/Git-Tools-Rewriting-History#Changing-Multiple-Commit-Messages) before submitting. 54 | 55 | **Happy coding**! 56 | -------------------------------------------------------------------------------- /src/Traits/ActionAssertions.php: -------------------------------------------------------------------------------- 1 | component->actions(NovaRequest::createFromGlobals()), 27 | new ArrayHasInstanceOf($action), 28 | $message 29 | ); 30 | 31 | return $this; 32 | } 33 | 34 | /** 35 | * Asserts that this component does not have the specified field. 36 | * 37 | * @param string $action The class path of the Action 38 | * @param string $message 39 | * @return $this 40 | */ 41 | public function assertActionMissing(string $action, string $message = ''): self 42 | { 43 | PHPUnit::assertThat( 44 | $this->component->actions(NovaRequest::createFromGlobals()), 45 | PHPUnit::logicalNot(new ArrayHasInstanceOf($action)), 46 | $message 47 | ); 48 | 49 | return $this; 50 | } 51 | 52 | /** 53 | * Asserts that this component has no Actions specified. 54 | * 55 | * @param string $message 56 | * @return $this 57 | */ 58 | public function assertHasNoActions(string $message = ''): self 59 | { 60 | PHPUnit::assertCount(0, $this->component->actions(NovaRequest::createFromGlobals()), $message); 61 | 62 | return $this; 63 | } 64 | 65 | /** 66 | * Asserts that all actions on this component are valid Actions. 67 | * 68 | * @param string $message 69 | * @return $this 70 | */ 71 | public function assertHasValidActions(string $message = ''): self 72 | { 73 | PHPUnit::assertThat( 74 | $this->component->actions(NovaRequest::createFromGlobals()), 75 | PHPUnit::logicalAnd( 76 | new IsType(IsType::TYPE_ARRAY), 77 | new TraversableContainsOnly(Action::class, false) 78 | ), 79 | $message 80 | ); 81 | 82 | return $this; 83 | } 84 | 85 | /** 86 | * Searches for a matching action instance on this component for testing. 87 | * 88 | * @param string $actionType The class name of the Action 89 | * @return MockActionElement 90 | * 91 | * @throws ActionNotFoundException 92 | */ 93 | public function action(string $actionType): MockActionElement 94 | { 95 | $actions = array_filter( 96 | $this->component->actions(NovaRequest::createFromGlobals()), 97 | fn ($a) => $a instanceof $actionType 98 | ); 99 | 100 | if (count($actions) === 0) { 101 | throw new ActionNotFoundException(); 102 | } 103 | 104 | return new MockActionElement(array_shift($actions)); 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /docs/filters.md: -------------------------------------------------------------------------------- 1 | # Filters 2 | 3 | * [Testing Filters](#testing-filters) 4 | * [Testing Applying Filter](#testing-applying-filter) 5 | * [Testing Filters on Components](#testing-filters-on-components) 6 | 7 | ## Testing Filters 8 | 9 | To create the testing object for a Nova Filter, add the `NovaFilterTest` trait to your class, and invoke the test method. 10 | 11 | ```php 12 | class TestClass extends TestCase 13 | { 14 | use NovaFilterTest; 15 | 16 | public function testNovaFilter() 17 | { 18 | $filter = $this->novaFilter(MyFilter::class); 19 | } 20 | } 21 | ``` 22 | 23 | The following assertions can be run on the Nova Filter: 24 | 25 | ### `assertSelectFilter()` 26 | 27 | ```php 28 | $filter->assertSelectFilter(); 29 | ``` 30 | 31 | Assert that the filter is a Select Filter 32 | 33 | ### `assertBooleanFilter()` 34 | 35 | ```php 36 | $filter->assertBooleanFilter(); 37 | ``` 38 | 39 | Assert that the filter is a Boolean Filter 40 | 41 | ### `assertDateFilter()` 42 | 43 | ```php 44 | $filter->assertDateFilter(); 45 | ``` 46 | 47 | Assert that the filter is a Date Filter 48 | 49 | ### `assertHasOption($option)` 50 | 51 | ```php 52 | $filter->assertHasOption('is_admin'); 53 | ``` 54 | 55 | Assert that the filter has an option with a key or value that matches `$option` 56 | 57 | ### `assertOptionMissing($option)` 58 | 59 | ```php 60 | $filter->assertOptionMissing('is_admin'); 61 | ``` 62 | 63 | Assert that the filter does not have an option with a key or value that matches `$option` 64 | 65 | ## Testing Applying Filter 66 | 67 | ```php 68 | $response = $filter->apply(User::class, 'is_admin'); 69 | ``` 70 | 71 | Invokes the `apply` method on the filter with the given parameters. 72 | 73 | * `$model` - The class path of the model to build the query from 74 | * `$value` - The value that would be passed by the user to the filter. The format of this value varies depending on the type of filter: 75 | * *Select Filter*: a single mixed value, generally a string 76 | * *Boolean Filter*: an array of keys with boolean values 77 | * *Date Filter*: a date, or a stringable Date object 78 | 79 | ### `assertContains($element)` 80 | 81 | ```php 82 | $response->assertContains(User::find(1)); 83 | ``` 84 | 85 | Assert that `$element` is returned when this filter is applied 86 | 87 | ### `assertMissing($element)` 88 | 89 | ```php 90 | $response->assertMissing(User::find(1)); 91 | ``` 92 | 93 | Assert that `$element` is not returned when this filter is applied 94 | 95 | ### `assertCount($count)` 96 | 97 | ```php 98 | $response->assertCount(3); 99 | ``` 100 | 101 | Assert that a specific number of records are returned when this filter is applied 102 | 103 | ## Testing Filters on Components 104 | 105 | Filter assertions can be run on the following Nova classes: 106 | 107 | * [Lenses](lenses.html#testing-lenses) 108 | * [Resources](resources.html#testing-resources) 109 | 110 | ### `assertHasFilter($filter)` 111 | 112 | ```php 113 | $component->assertHasFilter(MyFilter::class); 114 | ``` 115 | 116 | Asserts that the provided class path `$filter` matches one of the filters returned by the `filters()` method 117 | 118 | ### `assertFilterMissing($filter)` 119 | 120 | ```php 121 | $component->assertFilterMissing(MyFilter::class); 122 | ``` 123 | 124 | Asserts that the provided class path `$filter` does not match any filters returned by the `filters()` method 125 | 126 | ### `assertHasNoFilters()` 127 | 128 | ```php 129 | $component->assertHasNoFilters(); 130 | ``` 131 | 132 | Asserts that the `filters()` method returns an empty array. 133 | 134 | ### `assertHasValidFilters()` 135 | 136 | ```php 137 | $component->assertHasValidFilters(); 138 | ``` 139 | 140 | Asserts that all values returned by the `filters()` method are valid Nova Filters. 141 | -------------------------------------------------------------------------------- /src/Traits/FieldAssertions.php: -------------------------------------------------------------------------------- 1 | component->fields(NovaRequest::createFromGlobals()), 29 | new HasField($field, $this->allowPanels()), 30 | $message 31 | ); 32 | 33 | return $this; 34 | } 35 | 36 | /** 37 | * Checks that no field on this Nova component has a name or attribute matching 38 | * the given parameter. 39 | * 40 | * @param string $field 41 | * @param string $message 42 | * @return $this 43 | */ 44 | public function assertFieldMissing(string $field, string $message = ''): self 45 | { 46 | PHPUnit::assertThat( 47 | $this->component->fields(NovaRequest::createFromGlobals()), 48 | PHPUnit::logicalNot(new HasField($field, $this->allowPanels())), 49 | $message 50 | ); 51 | 52 | return $this; 53 | } 54 | 55 | /** 56 | * Asserts that this Nova Action has no fields defined. 57 | * 58 | * @param string $message 59 | * @return $this 60 | */ 61 | public function assertHasNoFields(string $message = ''): self 62 | { 63 | PHPUnit::assertCount(0, $this->component->fields(NovaRequest::createFromGlobals()), $message); 64 | 65 | return $this; 66 | } 67 | 68 | /** 69 | * Asserts that all fields defined on this Nova Action are valid fields. 70 | * 71 | * @param string $message 72 | * @return $this 73 | */ 74 | public function assertHasValidFields(string $message = ''): self 75 | { 76 | PHPUnit::assertThat( 77 | $this->component->fields(NovaRequest::createFromGlobals()), 78 | PHPUnit::logicalAnd( 79 | new IsType(IsType::TYPE_ARRAY), 80 | new HasValidFields($this->allowPanels()) 81 | ), 82 | $message 83 | ); 84 | 85 | return $this; 86 | } 87 | 88 | /** 89 | * Searches for a matching field instance on this component for testing. 90 | * 91 | * @param string $fieldName The name or attribute of the field to return 92 | * @return MockFieldElement 93 | * 94 | * @throws FieldNotFoundException 95 | */ 96 | public function field(string $fieldName): MockFieldElement 97 | { 98 | $field = FieldHelper::findField( 99 | $this->component->fields(NovaRequest::createFromGlobals()), 100 | $fieldName, 101 | $this->allowPanels() 102 | ); 103 | 104 | if (is_null($field)) { 105 | throw new FieldNotFoundException(); 106 | } 107 | 108 | return new MockFieldElement($field); 109 | } 110 | 111 | private function allowPanels(): bool 112 | { 113 | return $this instanceof MockLens || $this instanceof MockResource; 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/Lenses/MockLens.php: -------------------------------------------------------------------------------- 1 | model = $model; 24 | } 25 | 26 | /** 27 | * Assert that the query builder will return the given model. 28 | * 29 | * @deprecated 30 | * 31 | * @param Model $element The model contained in the query result 32 | * @param string $message 33 | * @return $this 34 | */ 35 | public function assertQueryContains(Model $element, string $message = ''): self 36 | { 37 | PHPUnit::assertThat( 38 | $this->component::query(new MockLensRequest(), $this->model::query())->get(), 39 | new EloquentCollectionContains($element), 40 | $message 41 | ); 42 | 43 | return $this; 44 | } 45 | 46 | /** 47 | * Assert that the query builder will not return the given model. 48 | * 49 | * @deprecated 50 | * 51 | * @param Model $element The model not contained in the query result 52 | * @param string $message 53 | * @return $this 54 | */ 55 | public function assertQueryMissing(Model $element, string $message = ''): self 56 | { 57 | PHPUnit::assertThat( 58 | $this->component::query(new MockLensRequest(), $this->model::query())->get(), 59 | PHPUnit::logicalNot(new EloquentCollectionContains($element)), 60 | $message 61 | ); 62 | 63 | return $this; 64 | } 65 | 66 | /** 67 | * Assert that the query builder uses the "withFilters" request. 68 | * 69 | * @deprecated 70 | * 71 | * @param string $message 72 | * @return $this 73 | */ 74 | public function assertQueryWithFilters(string $message = ''): self 75 | { 76 | $request = new MockLensRequest(); 77 | $this->component::query($request, $this->model::query()); 78 | PHPUnit::assertTrue($request->withFilters, $message); 79 | 80 | return $this; 81 | } 82 | 83 | /** 84 | * Assert that the query builder uses the "withOrdering" request. 85 | * 86 | * @deprecated 87 | * 88 | * @param string $message 89 | * @return $this 90 | */ 91 | public function assertQueryWithOrdering(string $message = ''): self 92 | { 93 | $request = new MockLensRequest(); 94 | $this->component::query($request, $this->model::query()); 95 | PHPUnit::assertTrue($request->withOrdering, $message); 96 | 97 | return $this; 98 | } 99 | 100 | /** 101 | * Apply lens query and test the response. 102 | * 103 | * @param string|null $model 104 | * @return MockLensQuery 105 | * 106 | * @throws InvalidModelException 107 | */ 108 | public function query(?string $model = null): MockLensQuery 109 | { 110 | if (! is_subclass_of($model, Model::class) && ! is_subclass_of($this->model, Model::class)) { 111 | throw new InvalidModelException(); 112 | } 113 | 114 | $query = ($model ?? $this->model)::query(); 115 | $request = new MockLensRequest(); 116 | 117 | return new MockLensQuery( 118 | $this->component::query($request, $query), 119 | $request 120 | ); 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /src/Filters/MockFilter.php: -------------------------------------------------------------------------------- 1 | component, 29 | PHPUnit::logicalAnd( 30 | new isInstanceOf(Filter::class), 31 | PHPUnit::logicalNot(new IsInstanceOf(BooleanFilter::class)), 32 | PHPUnit::logicalNot(new IsInstanceOf(DateFilter::class)) 33 | ), 34 | $message 35 | ); 36 | 37 | return $this; 38 | } 39 | 40 | /** 41 | * Assert that the subject filter is a boolean filter. 42 | * 43 | * @param string $message 44 | * @return $this 45 | */ 46 | public function assertBooleanFilter(string $message = ''): self 47 | { 48 | PHPUnit::assertInstanceOf(BooleanFilter::class, $this->component, $message); 49 | 50 | return $this; 51 | } 52 | 53 | /** 54 | * Assert that the subject filter is a date filter. 55 | * 56 | * @param string $message 57 | * @return $this 58 | */ 59 | public function assertDateFilter(string $message = ''): self 60 | { 61 | PHPUnit::assertInstanceOf(DateFilter::class, $this->component, $message); 62 | 63 | return $this; 64 | } 65 | 66 | /** 67 | * Assert that the filter has the given option. 68 | * 69 | * @param string $option The key or value of the option 70 | * @param string $message 71 | * @return $this 72 | */ 73 | public function assertHasOption(string $option, string $message = ''): self 74 | { 75 | PHPUnit::assertThat( 76 | $this->component->options(NovaRequest::createFromGlobals()), 77 | PHPUnit::logicalOr( 78 | new ArrayHasKey($option), 79 | new TraversableContainsEqual($option) 80 | ), 81 | $message 82 | ); 83 | 84 | return $this; 85 | } 86 | 87 | /** 88 | * Assert that the filter does not have the given option. 89 | * 90 | * @param string $option The key or value of the option 91 | * @param string $message 92 | * @return $this 93 | */ 94 | public function assertOptionMissing(string $option, string $message = ''): self 95 | { 96 | PHPUnit::assertThat( 97 | $this->component->options(NovaRequest::createFromGlobals()), 98 | PHPUnit::logicalNot( 99 | PHPUnit::logicalOr( 100 | new ArrayHasKey($option), 101 | new TraversableContainsEqual($option) 102 | ) 103 | ), 104 | $message 105 | ); 106 | 107 | return $this; 108 | } 109 | 110 | /** 111 | * Apply the filter with the provided value and allow tests on the result. 112 | * 113 | * @param string $model The model that the filter should be applied to 114 | * @param mixed|array $value The value returned by the filter 115 | * @return MockFilterQuery 116 | * 117 | * @throws InvalidModelException If the class provided is not a valid model class 118 | */ 119 | public function apply(string $model, $value): MockFilterQuery 120 | { 121 | if (! is_subclass_of($model, Model::class)) { 122 | throw new InvalidModelException(); 123 | } 124 | 125 | return new MockFilterQuery( 126 | $this->component->apply(NovaRequest::createFromGlobals(), $model::query(), $value) 127 | ); 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /src/Actions/MockActionResponse.php: -------------------------------------------------------------------------------- 1 | response = $response; 16 | } 17 | 18 | /** 19 | * Asserts the handle response is of the given type. 20 | * 21 | * @param string $type 22 | * @param string $message 23 | * @return $this 24 | */ 25 | public function assertResponseType(string $type, string $message = ''): self 26 | { 27 | PHPUnit::assertThat( 28 | $this->response, 29 | PHPUnit::logicalAnd( 30 | new IsType('array'), 31 | new IsActionResponseType($type) 32 | ), 33 | $message 34 | ); 35 | 36 | return $this; 37 | } 38 | 39 | /** 40 | * Asserts the handle response is of type "message". 41 | * 42 | * @param string $message 43 | * @return $this 44 | */ 45 | public function assertMessage(string $message = ''): self 46 | { 47 | return $this->assertResponseType('message', $message); 48 | } 49 | 50 | /** 51 | * Asserts the handle response is of type "danger". 52 | * 53 | * @param string $message 54 | * @return $this 55 | */ 56 | public function assertDanger(string $message = ''): self 57 | { 58 | return $this->assertResponseType('danger', $message); 59 | } 60 | 61 | /** 62 | * Asserts the handle response is of type "deleted". 63 | * 64 | * @param string $message 65 | * @return $this 66 | */ 67 | public function assertDeleted(string $message = ''): self 68 | { 69 | return $this->assertResponseType('deleted', $message); 70 | } 71 | 72 | /** 73 | * Asserts the handle response is of type "redirect". 74 | * 75 | * @param string $message 76 | * @return $this 77 | */ 78 | public function assertRedirect(string $message = ''): self 79 | { 80 | return $this->assertResponseType('redirect', $message); 81 | } 82 | 83 | /** 84 | * Asserts the handle response is of type "push". 85 | * 86 | * @param string $message 87 | * @return $this 88 | */ 89 | public function assertPush(string $message = ''): self 90 | { 91 | return $this->assertResponseType('push', $message); 92 | } 93 | 94 | /** 95 | * Asserts the handle response is of type "openInNewTab". 96 | * 97 | * @param string $message 98 | * @return $this 99 | */ 100 | public function assertOpenInNewTab(string $message = ''): self 101 | { 102 | return $this->assertResponseType('openInNewTab', $message); 103 | } 104 | 105 | /** 106 | * Asserts the handle response is of type "download". 107 | * 108 | * @param string $message 109 | * @return $this 110 | */ 111 | public function assertDownload(string $message = ''): self 112 | { 113 | return $this->assertResponseType('download', $message); 114 | } 115 | 116 | private function assertResponseContains(string $contents, string $type, string $message = ''): self 117 | { 118 | PHPUnit::assertThat( 119 | $this->response[$type] ?? '', 120 | PHPUnit::logicalAnd( 121 | PHPUnit::logicalNot(PHPUnit::isEmpty()), 122 | PHPUnit::stringContains($contents, true) 123 | ), 124 | $message 125 | ); 126 | 127 | return $this; 128 | } 129 | 130 | /** 131 | * Asserts the handle response is a "message" and contains the given text. 132 | * 133 | * @param string $contents The text to assert is in the response 134 | * @param string $message 135 | * @return $this 136 | */ 137 | public function assertMessageContains(string $contents, string $message = ''): self 138 | { 139 | return $this->assertResponseContains($contents, 'message', $message); 140 | } 141 | 142 | /** 143 | * Asserts the handle response is a "danger" and contains the given text. 144 | * 145 | * @param string $contents The text to assert is in the response 146 | * @param string $message 147 | * @return $this 148 | */ 149 | public function assertDangerContains(string $contents, string $message = ''): self 150 | { 151 | return $this->assertResponseContains($contents, 'danger', $message); 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /docs/fields.md: -------------------------------------------------------------------------------- 1 | # Fields 2 | 3 | * [Testing Fields on Components](#testing-fields-on-components) 4 | * [Testing Fields Individually](#testing-fields-individually) 5 | 6 | ## Testing Fields on Components 7 | 8 | Field assertions can be run on the following Nova classes: 9 | 10 | * [Actions](actions.md#testing-actions) 11 | * [Lenses](lenses.md#testing-lenses) 12 | * [Resources](resources.md#testing-resources) 13 | 14 | ### `assertHasField($field)` 15 | 16 | ```php 17 | $component->assertHasField('field_name'); 18 | ``` 19 | 20 | Asserts that the provided string matches the name or attribute of one of the fields returned by the `fields()` method 21 | 22 | ### `assertFieldMissing($field)` 23 | 24 | ```php 25 | $component->assertFieldMissing('field_name'); 26 | ``` 27 | 28 | Asserts that the provided string does not match the name or attribute of one of the fields returned by the `fields()` method 29 | 30 | ### `assertHasNoFields()` 31 | 32 | ```php 33 | $component->assertHasNoFields(); 34 | ``` 35 | 36 | Asserts that the `fields()` method returns an empty array. 37 | 38 | ### `assertHasValidFields()` 39 | 40 | ```php 41 | $component->assertHasValidFields(); 42 | ``` 43 | 44 | Asserts that all fields returned by the `fields()` method are valid. Examples of valid fields include: 45 | * `Field` instances 46 | * `FieldElement` instances 47 | * `Panel` instances (including any nested objects) 48 | 49 | ## Testing Fields Individually 50 | 51 | To test configuration of a component's individual fields, you may use the `field($fieldName)` method: 52 | 53 | ```php 54 | $field = $component->field('field_name'); 55 | ``` 56 | 57 | Searches the components `fields()` array for a field with a name or attribute matching `$fieldname`, and returns first occurrence in a testing class. 58 | 59 | ### `assertHasRule($rule)` 60 | 61 | ```php 62 | $field->assertHasRule('required'); 63 | ``` 64 | 65 | Asserts that the following rule is being applied to the value of this field. This assertion only works on string-type rules, not closures. 66 | 67 | ### `assertRuleMissing($rule)` 68 | 69 | ```php 70 | $field->assertRuleMissing('required'); 71 | ``` 72 | 73 | Asserts that the following rule is not being applied to the value of this field. This assertion only works on string-type rules, not closures. 74 | 75 | ### `assertHasCreationRule($rule)` 76 | 77 | ```php 78 | $field->assertHasCreationRule('required'); 79 | ``` 80 | 81 | Asserts that the following rule is being applied to the value of this field when a resource is created. This assertion only works on string-type rules, not closures. 82 | 83 | ### `assertCreationRuleMissing($rule)` 84 | 85 | ```php 86 | $field->assertCreationRuleMissing('required'); 87 | ``` 88 | 89 | Asserts that the following rule is not being applied to the value of this field when a resource is created. This assertion only works on string-type rules, not closures. 90 | 91 | ### `assertHasUpdateRule($rule)` 92 | 93 | ```php 94 | $field->assertHasUpdateRule('required'); 95 | ``` 96 | 97 | Asserts that the following rule is being applied to the value of this field when a resource is updated. This assertion only works on string-type rules, not closures. 98 | 99 | ### `assertUpdateRuleMissing($rule)` 100 | 101 | ```php 102 | $field->assertUpdateRuleMissing('required'); 103 | ``` 104 | 105 | Asserts that the following rule is not being applied to the value of this field when a resource is updated. This assertion only works on string-type rules, not closures. 106 | 107 | ### `assertShownOnIndex()` 108 | 109 | ```php 110 | $field->assertShownOnIndex(); 111 | ``` 112 | 113 | Asserts that the field will be shown on the component's index view. 114 | 115 | ### `assertHiddenFromIndex()` 116 | 117 | ```php 118 | $field->assertHiddenFromIndex(); 119 | ``` 120 | 121 | Asserts that the field will be hidden from the component's index view. 122 | 123 | ### `assertShownOnDetail()` 124 | 125 | ```php 126 | $field->assertShownOnDetail(); 127 | ``` 128 | 129 | Asserts that the field will be shown on the component's detail view. 130 | 131 | ### `assertHiddenFromDetail()` 132 | 133 | ```php 134 | $field->assertHiddenFromDetail(); 135 | ``` 136 | 137 | Asserts that the field will be hidden from the component's detail view. 138 | 139 | ### `assertNullable()` 140 | 141 | ```php 142 | $field->assertNullable(); 143 | ``` 144 | 145 | Asserts that the value of this field will be set to null if left empty. 146 | 147 | ### `assertNotNullable()` 148 | 149 | ```php 150 | $field->assertNotNullable(); 151 | ``` 152 | 153 | Asserts that the value of this field will not be set to null if left empty. 154 | 155 | ### `assertSortable()` 156 | 157 | ```php 158 | $field->assertSortable(); 159 | ``` 160 | 161 | Asserts that the component's records can be sorted by this field. 162 | 163 | ### `assertNotSortable()` 164 | 165 | ```php 166 | $field->assertNotSortable(); 167 | ``` 168 | 169 | Asserts that the component's records cannot be sorted by this field. 170 | -------------------------------------------------------------------------------- /docs/actions.md: -------------------------------------------------------------------------------- 1 | # Actions 2 | 3 | * [Testing Actions](#testing-actions) 4 | * [Testing Action Handle](#testing-action-handle) 5 | * [Testing Actions on Components](#testing-actions-on-components) 6 | * [Testing Actions Individually](#testing-actions-individually) 7 | 8 | ## Testing Actions 9 | 10 | To create the testing object for a Nova Action, add the `NovaActionTest` trait to your class, and invoke the test method. 11 | 12 | ```php 13 | class TestClass extends TestCase 14 | { 15 | use NovaActionTest; 16 | 17 | public function testNovaAction() 18 | { 19 | $action = $this->novaAction(MyAction::class); 20 | } 21 | } 22 | ``` 23 | 24 | The following assertions can be run on the Nova Action: 25 | 26 | * [Fields](fields.md#testing-fields-on-components) 27 | 28 | ## Testing Action Handle 29 | 30 | ```php 31 | $response = $action->handle($fields, $models); 32 | ``` 33 | 34 | Invokes the `handle` method on the action with the given parameters. 35 | 36 | * `$fields` - A key-value array with the input values of the action fields, indexed by attribute. Eg., `['name' => 'John Smith']` 37 | * `$models` - A list of the models to apply the action to. Value can be either an array, an Eloquent collection, or a single Model. 38 | 39 | ### `assertMessage()` 40 | 41 | ```php 42 | $response->assertMessage(); 43 | ``` 44 | 45 | Assert that this action will return an `Action::message()` response with the given input 46 | 47 | ### `assertDanger()` 48 | 49 | ```php 50 | $response->assertDanger(); 51 | ``` 52 | 53 | Assert that this action will return an `Action::danger()` response with the given input 54 | 55 | ### `assertDeleted()` 56 | 57 | ```php 58 | $response->assertDeleted(); 59 | ``` 60 | 61 | Assert that this action will return an `Action::deleted()` response with the given input 62 | 63 | ### `assertRedirect()` 64 | 65 | ```php 66 | $response->assertRedirect(); 67 | ``` 68 | 69 | Assert that this action will return an `Action::redirect()` response with the given input 70 | 71 | ### `assertPush()` 72 | 73 | ```php 74 | $response->assertPush(); 75 | ``` 76 | 77 | Assert that this action will return an `Action::push()` response with the given input 78 | 79 | ### `assertOpenInNewTab()` 80 | 81 | ```php 82 | $response->assertOpenInNewTab(); 83 | ``` 84 | 85 | Assert that this action will return an `Action::openInNewTab()` response with the given input 86 | 87 | ### `assertDownload()` 88 | 89 | ```php 90 | $response->assertDownload(); 91 | ``` 92 | 93 | Assert that this action will return an `Action::download()` response with the given input 94 | 95 | ### `assertMessageContains($contents)` 96 | 97 | ```php 98 | $response->assertMessageContains('Success'); 99 | ``` 100 | 101 | Assert that the `Action::message()` response contains `$contents` 102 | 103 | ### `assertDangerContains($contents)` 104 | 105 | ```php 106 | $response->assertDangerContains('Failure'); 107 | ``` 108 | 109 | Assert that the `Action::danger()` response contains `$contents` 110 | 111 | ## Testing Actions on Components 112 | 113 | Action assertions can be run on the following Nova classes: 114 | 115 | * [Lenses](lenses.md#testing-lenses) 116 | * [Resources](resources.md#testing-resources) 117 | 118 | ### `assertHasAction($action)` 119 | 120 | ```php 121 | $component->assertHasAction(MyAction::class); 122 | ``` 123 | 124 | Asserts that the provided class path `$action` matches one of the actions returned by the `actions()` method 125 | 126 | ### `assertActionMissing($action)` 127 | 128 | ```php 129 | $component->assertActionMissing(MyAction::class); 130 | ``` 131 | 132 | Asserts that the provided class path `$action` does not match any actions returned by the `actions()` method 133 | 134 | ### `assertHasNoAction()` 135 | 136 | ```php 137 | $component->assertHasNoActions(); 138 | ``` 139 | 140 | Asserts that the `actions()` method returns an empty array. 141 | 142 | ### `assertHasValidActions()` 143 | 144 | ```php 145 | $component->assertHasValidActions(); 146 | ``` 147 | 148 | Asserts that all actions returned by the `actions()` method are valid Nova Actions. 149 | 150 | ## Testing Actions Individually 151 | 152 | ```php 153 | $action = $component->action(MyAction::class); 154 | ``` 155 | 156 | Searches the `actions()` method on the component for the first occurance of an action matching the provided class name, and returns a testing object. 157 | 158 | ### `assertShownOnIndex()` 159 | 160 | ```php 161 | $action->assertShownOnIndex(); 162 | ``` 163 | 164 | Asserts that the action will be shown on this component's index view. 165 | 166 | ### `assertHiddenFromIndex()` 167 | 168 | ```php 169 | $action->assertHiddenFromIndex(); 170 | ``` 171 | 172 | Asserts that the action will be hidden from this component's index view. 173 | 174 | ### `assertShownOnDetail()` 175 | 176 | ```php 177 | $action->assertShownOnDetail(); 178 | ``` 179 | 180 | Asserts that the action will be shown on this component's detail view. 181 | 182 | ### `assertHiddenFromDetail()` 183 | 184 | ```php 185 | $action->assertHiddenFromDetail(); 186 | ``` 187 | 188 | Asserts that the action will be hidden from this component's detail view. 189 | 190 | ### `assertShownInline()` 191 | 192 | ```php 193 | $action->assertShownInline(); 194 | ``` 195 | 196 | Asserts that the action will be shown on this component's table row view. 197 | 198 | ### `assertNotShownInline()` 199 | 200 | ```php 201 | $action->assertNotShownInline(); 202 | ``` 203 | 204 | Asserts that the action will be hidden from this component's table row view. 205 | -------------------------------------------------------------------------------- /.php-cs-fixer.dist.php: -------------------------------------------------------------------------------- 1 | ['syntax' => 'short'], 8 | 'align_multiline_comment' => ['comment_type' => 'phpdocs_only'], 9 | 'binary_operator_spaces' => [ 10 | 'default' => 'single_space', 11 | ], 12 | 'blank_line_after_namespace' => true, 13 | 'blank_line_after_opening_tag' => true, 14 | 'blank_line_before_statement' => [ 15 | 'statements' => ['return'], 16 | ], 17 | 'braces' => [ 18 | 'position_after_anonymous_constructs' => 'same', 19 | ], 20 | 'cast_spaces' => true, 21 | 'class_attributes_separation' => [ 22 | 'elements' => ['method' => 'one', 'trait_import' => 'none'], 23 | ], 24 | 'class_definition' => true, 25 | 'concat_space' => [ 26 | 'spacing' => 'none', 27 | ], 28 | 'constant_case' => ['case' => 'lower'], 29 | 'declare_equal_normalize' => true, 30 | 'elseif' => true, 31 | 'encoding' => true, 32 | 'full_opening_tag' => true, 33 | 'fully_qualified_strict_types' => true, // added by Shift 34 | 'function_declaration' => true, 35 | 'function_typehint_space' => true, 36 | 'general_phpdoc_tag_rename' => true, 37 | 'heredoc_to_nowdoc' => true, 38 | 'include' => true, 39 | 'increment_style' => ['style' => 'post'], 40 | 'indentation_type' => true, 41 | 'linebreak_after_opening_tag' => true, 42 | 'line_ending' => true, 43 | 'lowercase_cast' => true, 44 | 'lowercase_keywords' => true, 45 | 'lowercase_static_reference' => true, // added from Symfony 46 | 'magic_method_casing' => true, // added from Symfony 47 | 'magic_constant_casing' => true, 48 | 'method_argument_space' => true, 49 | 'native_function_casing' => true, 50 | 'no_alias_functions' => true, 51 | 'no_extra_blank_lines' => [ 52 | 'tokens' => [ 53 | 'extra', 54 | 'throw', 55 | 'use', 56 | ], 57 | ], 58 | 'no_blank_lines_after_class_opening' => true, 59 | 'no_blank_lines_after_phpdoc' => true, 60 | 'no_closing_tag' => true, 61 | 'no_empty_phpdoc' => true, 62 | 'no_empty_statement' => true, 63 | 'no_leading_import_slash' => true, 64 | 'no_leading_namespace_whitespace' => true, 65 | 'no_mixed_echo_print' => [ 66 | 'use' => 'echo', 67 | ], 68 | 'no_multiline_whitespace_around_double_arrow' => true, 69 | 'multiline_whitespace_before_semicolons' => [ 70 | 'strategy' => 'no_multi_line', 71 | ], 72 | 'no_short_bool_cast' => true, 73 | 'no_singleline_whitespace_before_semicolons' => true, 74 | 'no_spaces_after_function_name' => true, 75 | 'no_spaces_around_offset' => true, 76 | 'no_spaces_inside_parenthesis' => true, 77 | 'no_trailing_comma_in_list_call' => true, 78 | 'no_trailing_comma_in_singleline_array' => true, 79 | 'no_trailing_whitespace' => true, 80 | 'no_trailing_whitespace_in_comment' => true, 81 | 'no_unneeded_control_parentheses' => true, 82 | 'no_unreachable_default_argument_value' => true, 83 | 'no_unused_imports' => true, 84 | 'no_useless_return' => true, 85 | 'no_whitespace_before_comma_in_array' => true, 86 | 'no_whitespace_in_blank_line' => true, 87 | 'normalize_index_brace' => true, 88 | 'not_operator_with_successor_space' => true, 89 | 'object_operator_without_whitespace' => true, 90 | 'ordered_imports' => ['sort_algorithm' => 'alpha'], 91 | 'phpdoc_indent' => true, 92 | 'phpdoc_inline_tag_normalizer' => true, 93 | 'phpdoc_no_access' => true, 94 | 'phpdoc_no_package' => true, 95 | 'phpdoc_no_useless_inheritdoc' => true, 96 | 'phpdoc_scalar' => true, 97 | 'phpdoc_single_line_var_spacing' => true, 98 | 'phpdoc_summary' => true, 99 | 'phpdoc_tag_type' => [ 100 | 'tags' => ['inheritdoc' => 'inline'], 101 | ], 102 | 'phpdoc_to_comment' => true, 103 | 'phpdoc_trim' => true, 104 | 'phpdoc_types' => true, 105 | 'phpdoc_var_without_name' => true, 106 | 'psr_autoloading' => true, 107 | 'self_accessor' => true, 108 | 'short_scalar_cast' => true, 109 | 'simplified_null_return' => true, 110 | 'single_blank_line_at_eof' => true, 111 | 'single_blank_line_before_namespace' => true, 112 | 'single_class_element_per_statement' => true, 113 | 'single_import_per_statement' => true, 114 | 'single_line_after_imports' => true, 115 | 'single_line_comment_style' => [ 116 | 'comment_types' => ['hash'], 117 | ], 118 | 'single_quote' => true, 119 | 'space_after_semicolon' => true, 120 | 'standardize_not_equals' => true, 121 | 'switch_case_semicolon_to_colon' => true, 122 | 'switch_case_space' => true, 123 | 'ternary_operator_spaces' => true, 124 | 'trailing_comma_in_multiline' => [ 125 | 'elements' => ['arrays'], 126 | ], 127 | 'trim_array_spaces' => true, 128 | 'unary_operator_spaces' => true, 129 | 'visibility_required' => [ 130 | 'elements' => ['method', 'property'], 131 | ], 132 | 'whitespace_after_comma_in_array' => true, 133 | ]; 134 | $finder = Finder::create() 135 | ->notPath('bootstrap') 136 | ->notPath('storage') 137 | ->notPath('vendor') 138 | ->in(getcwd()) 139 | ->name('*.php') 140 | ->notName('*.blade.php') 141 | ->notName('index.php') 142 | ->notName('server.php') 143 | ->notName('_ide_helper.php') 144 | ->ignoreDotFiles(true) 145 | ->ignoreVCS(true); 146 | 147 | return (new Config) 148 | ->setFinder($finder) 149 | ->setRules($rules) 150 | ->setRiskyAllowed(true) 151 | ->setUsingCache(true); 152 | -------------------------------------------------------------------------------- /src/Fields/MockFieldElement.php: -------------------------------------------------------------------------------- 1 | field = $field; 15 | } 16 | 17 | /** 18 | * Assert that the following rule can be found on the field. 19 | * 20 | * @param string $rule The rule to match this field against 21 | * @param string $message 22 | * @return $this 23 | */ 24 | public function assertHasRule(string $rule, string $message = ''): self 25 | { 26 | PHPUnit::assertContains($rule, $this->field->rules, $message); 27 | 28 | return $this; 29 | } 30 | 31 | /** 32 | * Assert that the following rule cannot be found on the field. 33 | * 34 | * @param string $rule The rule to match this field against 35 | * @param string $message 36 | * @return $this 37 | */ 38 | public function assertRuleMissing(string $rule, string $message = ''): self 39 | { 40 | PHPUnit::assertNotContains($rule, $this->field->rules, $message); 41 | 42 | return $this; 43 | } 44 | 45 | /** 46 | * Assert that the following rule can be found on the field when a 47 | * resource being created. 48 | * 49 | * @param string $rule The rule to match this field against 50 | * @param string $message 51 | * @return $this 52 | */ 53 | public function assertHasCreationRule(string $rule, string $message = ''): self 54 | { 55 | PHPUnit::assertContains($rule, $this->field->creationRules, $message); 56 | 57 | return $this; 58 | } 59 | 60 | /** 61 | * Assert that the following rule cannot be found on the field when a 62 | * resource being created. 63 | * 64 | * @param string $rule The rule to match this field against 65 | * @param string $message 66 | * @return $this 67 | */ 68 | public function assertCreationRuleMissing(string $rule, string $message = ''): self 69 | { 70 | PHPUnit::assertNotContains($rule, $this->field->creationRules, $message); 71 | 72 | return $this; 73 | } 74 | 75 | /** 76 | * Assert that the following rule can be found on the field when a 77 | * resource being updated. 78 | * 79 | * @param string $rule The rule to match this field against 80 | * @param string $message 81 | * @return $this 82 | */ 83 | public function assertHasUpdateRule(string $rule, string $message = ''): self 84 | { 85 | PHPUnit::assertContains($rule, $this->field->updateRules, $message); 86 | 87 | return $this; 88 | } 89 | 90 | /** 91 | * Assert that the following rule cannot be found on the field when a 92 | * resource being created. 93 | * 94 | * @param string $rule The rule to match this field against 95 | * @param string $message 96 | * @return $this 97 | */ 98 | public function assertUpdateRuleMissing(string $rule, string $message = ''): self 99 | { 100 | PHPUnit::assertNotContains($rule, $this->field->updateRules, $message); 101 | 102 | return $this; 103 | } 104 | 105 | /** 106 | * Assert that the field can be shown on the index view. 107 | * 108 | * @param string $message 109 | * @return $this 110 | */ 111 | public function assertShownOnIndex(string $message = ''): self 112 | { 113 | $test = $this->field->showOnIndex; 114 | PHPUnit::assertTrue( 115 | is_callable($test) ? $test() : $test, 116 | $message 117 | ); 118 | 119 | return $this; 120 | } 121 | 122 | /** 123 | * Assert that the field is hidden from the index view. 124 | * 125 | * @param string $message 126 | * @return $this 127 | */ 128 | public function assertHiddenFromIndex(string $message = ''): self 129 | { 130 | $test = $this->field->showOnIndex; 131 | PHPUnit::assertFalse( 132 | is_callable($test) ? $test() : $test, 133 | $message 134 | ); 135 | 136 | return $this; 137 | } 138 | 139 | /** 140 | * Assert that the field can be shown on the detail view. 141 | * 142 | * @param string $message 143 | * @return $this 144 | */ 145 | public function assertShownOnDetail(string $message = ''): self 146 | { 147 | $test = $this->field->showOnDetail; 148 | PHPUnit::assertTrue( 149 | is_callable($test) ? $test() : $test, 150 | $message 151 | ); 152 | 153 | return $this; 154 | } 155 | 156 | /** 157 | * Assert that the field is hidden from the detail view. 158 | * 159 | * @param string $message 160 | * @return $this 161 | */ 162 | public function assertHiddenFromDetail(string $message = ''): self 163 | { 164 | $test = $this->field->showOnDetail; 165 | PHPUnit::assertFalse( 166 | is_callable($test) ? $test() : $test, 167 | $message 168 | ); 169 | 170 | return $this; 171 | } 172 | 173 | /** 174 | * Assert that the field can be shown when creating a new record. 175 | * 176 | * @param string $message 177 | * @return $this 178 | */ 179 | public function assertShownWhenCreating(string $message = ''): self 180 | { 181 | $test = $this->field->showOnCreation; 182 | PHPUnit::assertTrue( 183 | is_callable($test) ? $test() : $test, 184 | $message 185 | ); 186 | 187 | return $this; 188 | } 189 | 190 | /** 191 | * Assert that the field is hidden when creating a new record. 192 | * 193 | * @param string $message 194 | * @return $this 195 | */ 196 | public function assertHiddenWhenCreating(string $message = ''): self 197 | { 198 | $test = $this->field->showOnCreation; 199 | PHPUnit::assertFalse( 200 | is_callable($test) ? $test() : $test, 201 | $message 202 | ); 203 | 204 | return $this; 205 | } 206 | 207 | /** 208 | * Assert that the field can be shown when updating a record. 209 | * 210 | * @param string $message 211 | * @return $this 212 | */ 213 | public function assertShownWhenUpdating(string $message = ''): self 214 | { 215 | $test = $this->field->showOnUpdate; 216 | PHPUnit::assertTrue( 217 | is_callable($test) ? $test() : $test, 218 | $message 219 | ); 220 | 221 | return $this; 222 | } 223 | 224 | /** 225 | * Assert that the field is hidden when updating a record. 226 | * 227 | * @param string $message 228 | * @return $this 229 | */ 230 | public function assertHiddenWhenUpdating(string $message = ''): self 231 | { 232 | $test = $this->field->showOnUpdate; 233 | PHPUnit::assertFalse( 234 | is_callable($test) ? $test() : $test, 235 | $message 236 | ); 237 | 238 | return $this; 239 | } 240 | 241 | /** 242 | * Assert that the field should be set to null if the value is empty. 243 | * 244 | * @param string $message 245 | * @return $this 246 | */ 247 | public function assertNullable(string $message = ''): self 248 | { 249 | PHPUnit::assertTrue($this->field->nullable, $message); 250 | 251 | return $this; 252 | } 253 | 254 | /** 255 | * Assert that the field should not be set to null if the value is empty. 256 | * 257 | * @param string $message 258 | * @return $this 259 | */ 260 | public function assertNotNullable(string $message = ''): self 261 | { 262 | PHPUnit::assertFalse($this->field->nullable, $message); 263 | 264 | return $this; 265 | } 266 | 267 | /** 268 | * Assert that records can be sorted by this field. 269 | * 270 | * @param string $message 271 | * @return $this 272 | */ 273 | public function assertSortable(string $message = ''): self 274 | { 275 | PHPUnit::assertTrue($this->field->sortable, $message); 276 | 277 | return $this; 278 | } 279 | 280 | /** 281 | * Assert that records cannot be sorted by this field. 282 | * 283 | * @param string $message 284 | * @return $this 285 | */ 286 | public function assertNotSortable(string $message = ''): self 287 | { 288 | PHPUnit::assertFalse($this->field->sortable, $message); 289 | 290 | return $this; 291 | } 292 | } 293 | --------------------------------------------------------------------------------