From 1a1f5c0a853e6ffa57f47297ba1d43db7d5a6593 Mon Sep 17 00:00:00 2001 From: markhuot Date: Sat, 18 Nov 2023 05:08:25 -0500 Subject: [PATCH] improving code coverage --- .run/Pest.run.xml | 1 - composer.json | 2 +- phpunit.xml | 3 +- src/behaviors/BodyParamObjectBehavior.php | 32 ++++++- src/behaviors/RenderFieldHtmlBehavior.php | 6 ++ src/controllers/ComponentsController.php | 20 ++--- src/factories/Component.php | 4 + src/models/http/AddComponentRequest.php | 2 +- src/models/http/MoveComponentRequest.php | 28 ++---- tests/RouteTest.php | 105 ++++++++++++++++++++-- 10 files changed, 155 insertions(+), 48 deletions(-) diff --git a/.run/Pest.run.xml b/.run/Pest.run.xml index 5c65707..125630b 100644 --- a/.run/Pest.run.xml +++ b/.run/Pest.run.xml @@ -1,6 +1,5 @@ - diff --git a/composer.json b/composer.json index 32cb83b..f61f28e 100644 --- a/composer.json +++ b/composer.json @@ -22,7 +22,7 @@ } ], "require-dev": { - "markhuot/craft-pest-core": "dev-sequence", + "markhuot/craft-pest-core": "dev-main", "phpstan/phpstan": "^1.10", "laravel/pint": "^1.13", "craftcms/craft": "dev-main" diff --git a/phpunit.xml b/phpunit.xml index 5ac8c23..6673992 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -10,8 +10,7 @@ - ./modules - ./templates + ./src diff --git a/src/behaviors/BodyParamObjectBehavior.php b/src/behaviors/BodyParamObjectBehavior.php index a4166ef..f42c29b 100644 --- a/src/behaviors/BodyParamObjectBehavior.php +++ b/src/behaviors/BodyParamObjectBehavior.php @@ -43,6 +43,29 @@ public function getBodyParamObject(string $class, string $formName = '') return $this->handleData($data, $class, $formName); } + /** + * @template T + * + * @param class-string $class + * @return T + */ + public function getBodyParamObjectOrFail(string $class, string $formName = '') + { + if (! $this->owner->getIsPost()) { + throw new BadRequestHttpException('Post request required'); + } + + // Get the post data + $data = $this->owner->getBodyParams(); + + $data = $this->handleData($data, $class, $formName); + if ($data->errors) { + throw new \RuntimeException(implode(', ', $data->getErrorSummary(true))); + } + + return $data; + } + /** * @template T * @@ -56,7 +79,12 @@ public function getQueryParamObject(string $class, string $formName = '') public function getQueryParamObjectOrFail(string $class, string $formName = '') { - return $this->handleData($this->owner->getQueryParams(), $class, $formName, true, false); + $data = $this->handleData($this->owner->getQueryParams(), $class, $formName); + if ($data->errors) { + throw new \RuntimeException(implode(', ', $data->getErrorSummary(true))); + } + + return $data; } /** @@ -87,7 +115,7 @@ protected function handleData(array $data, string $class, string $formName = '', // and render HTML or throw the exception if it's called ->withoutExceptionHandling, but that's // not possible today so we're going to ignore it and come back to it later. // @phpstan-ignore-next-line - if (function_exists('test') && test()->shouldSkipExceptionHandling() && ! empty($model->errors)) { + if (function_exists('test') && test()->shouldRenderExceptionsAsHtml() && ! empty($model->errors)) { throw new \RuntimeException(collect($model->errors)->flatten()->join(' ')); } } else { diff --git a/src/behaviors/RenderFieldHtmlBehavior.php b/src/behaviors/RenderFieldHtmlBehavior.php index e0b0314..17b192c 100644 --- a/src/behaviors/RenderFieldHtmlBehavior.php +++ b/src/behaviors/RenderFieldHtmlBehavior.php @@ -2,9 +2,11 @@ namespace markhuot\keystone\behaviors; +use Craft; use craft\base\ElementInterface; use craft\base\FieldInterface; use craft\fieldlayoutelements\CustomField; +use craft\web\View; use yii\base\Behavior; /** @@ -14,10 +16,14 @@ class RenderFieldHtmlBehavior extends Behavior { public function getFieldHtml(FieldInterface $field) { + $oldTemplateMode = Craft::$app->getView()->getTemplateMode(); + Craft::$app->getView()->setTemplateMode(View::TEMPLATE_MODE_CP); + foreach ($this->owner->getFieldLayout()->createForm($this->owner)->tabs as $tab) { foreach ($tab->elements as [$fieldLayout, $isConditional, $fieldHtml]) { if ($fieldLayout instanceof CustomField) { if ($fieldLayout->getField()->handle === $field->handle) { + Craft::$app->getView()->setTemplateMode($oldTemplateMode); return $fieldHtml; } } diff --git a/src/controllers/ComponentsController.php b/src/controllers/ComponentsController.php index e60fae2..a9288ba 100644 --- a/src/controllers/ComponentsController.php +++ b/src/controllers/ComponentsController.php @@ -24,7 +24,7 @@ class ComponentsController extends Controller { public function actionAdd() { - $data = $this->request->getQueryParamObject(AddComponentRequest::class); + $data = $this->request->getQueryParamObjectOrFail(AddComponentRequest::class); $parent = (new GetParentFromPath)->handle($data->element->id, $data->field->id, $data->path); return $this->asCpScreen() @@ -43,7 +43,7 @@ public function actionAdd() public function actionStore() { - $data = $this->request->getBodyParamObject(StoreComponentRequest::class); + $data = $this->request->getBodyParamObjectOrFail(StoreComponentRequest::class); (new AddComponent)->handle( elementId: $data->element->id, @@ -79,7 +79,7 @@ public function actionEdit() public function actionUpdate() { - $component = $this->request->getBodyParamObject(Component::class); + $component = $this->request->getBodyParamObjectOrFail(Component::class); $fields = $this->request->getBodyParam('fields', []); (new EditComponentData)->handle($component, $fields); @@ -89,7 +89,7 @@ public function actionUpdate() public function actionDelete() { - $component = $this->request->getBodyParamObject(Component::class); + $component = $this->request->getBodyParamObjectOrFail(Component::class); (new DeleteComponent)->handle($component); return $this->asSuccess('Component deleted', [ @@ -99,21 +99,11 @@ public function actionDelete() public function actionMove() { - $data = $this->request->getBodyParamObject(MoveComponentRequest::class); + $data = $this->request->getBodyParamObjectOrFail(MoveComponentRequest::class); (new MoveComponent)->handle($data->source, $data->position, $data->target, $data->slot); return $this->asSuccess('Component moved', [ 'fieldHtml' => $data->getTargetElement()->getFieldHtml($data->getTargetField()), ]); } - - public function actionGetEditModalHtml() - { - $id = $this->request->getRequiredBodyParam('id'); - $component = Component::findOne(['id' => $id]); - - return Craft::$app->getView()->renderTemplate('keystone/builder/edit', [ - 'component' => $component, - ]); - } } diff --git a/src/factories/Component.php b/src/factories/Component.php index f032325..ae57510 100644 --- a/src/factories/Component.php +++ b/src/factories/Component.php @@ -9,6 +9,10 @@ use markhuot\keystone\models\ComponentData; use SplObjectStorage; +/** + * @method self type(string $type) + * @method \markhuot\keystone\models\Component create(array $attributes = []) + */ class Component extends Factory { public static $tests; diff --git a/src/models/http/AddComponentRequest.php b/src/models/http/AddComponentRequest.php index 801e239..924240f 100644 --- a/src/models/http/AddComponentRequest.php +++ b/src/models/http/AddComponentRequest.php @@ -4,7 +4,7 @@ use craft\base\ElementInterface; use craft\base\FieldInterface; -use craft\base\Model; +use markhuot\keystone\base\Model; use markhuot\keystone\validators\Required; use markhuot\keystone\validators\Safe; diff --git a/src/models/http/MoveComponentRequest.php b/src/models/http/MoveComponentRequest.php index 9f30a0f..7c14afd 100644 --- a/src/models/http/MoveComponentRequest.php +++ b/src/models/http/MoveComponentRequest.php @@ -5,43 +5,29 @@ use Craft; use craft\base\ElementInterface; use craft\base\FieldInterface; -use craft\base\Model; +use markhuot\keystone\base\Model; use markhuot\keystone\enums\MoveComponentPosition; use markhuot\keystone\models\Component; +use markhuot\keystone\validators\Required; +use markhuot\keystone\validators\Safe; use function markhuot\keystone\helpers\base\app; use function markhuot\keystone\helpers\base\throw_if; class MoveComponentRequest extends Model { + #[Required] public Component $source; + #[Required] public Component $target; + #[Required] public MoveComponentPosition $position; + #[Safe] public ?string $slot = null; - /** - * @return array - */ - public function safeAttributes(): array - { - return [...parent::safeAttributes(), 'slot']; - } - - /** - * @return array - */ - public function rules(): array - { - return [ - ['source', 'required'], - ['target', 'required'], - ['position', 'required'], - ]; - } - public function getTargetElement(): ElementInterface { // We ignore the next line for phpstan because Craft types on the second argument diff --git a/tests/RouteTest.php b/tests/RouteTest.php index 5dc94c2..f7d6153 100644 --- a/tests/RouteTest.php +++ b/tests/RouteTest.php @@ -1,7 +1,102 @@ skip() - ->actingAsAdmin() - ->get('/keystone/components/edit') - ->assertOk(); +use craft\helpers\UrlHelper; +use markhuot\craftpest\factories\Entry; +use markhuot\keystone\models\Component; + +it('loads add panel', function () { + $component = Component::factory()->type('keystone/text')->create(); + + $this->actingAsAdmin() + ->get(UrlHelper::actionUrl('keystone/components/add', [ + 'elementId' => $component->elementId, + 'fieldId' => $component->fieldId, + 'path' => $component->path, + 'sortOrder' => 1, + ])) + ->assertOk(); +}); + +it('loads edit panel', function ($type) { + $component = Component::factory()->type($type)->create(); + + $this->actingAsAdmin() + ->get(UrlHelper::cpUrl('keystone/components/edit', $component->getQueryCondition())) + ->assertOk(); +})->with([ + 'keystone/asset', 'keystone/elementquery', 'keystone/fragment', + 'keystone/icon', 'keystone/link', 'keystone/heading', + 'keystone/section', 'keystone/text', +]); + +it('stores a component', function () { + $component = Component::factory() + ->type('keystone/section') + ->elementId(Entry::factory()->section('pages')->create()->id) + ->create(); + + $this->actingAsAdmin() + ->postJson(UrlHelper::actionUrl('keystone/components/store'), [ + 'elementId' => $component->elementId, + 'fieldId' => $component->fieldId, + 'sortOrder' => 1, + 'path' => $component->getChildPath(), + 'type' => 'keystone/text', + ]) + ->assertOk() + ->assertJsonPath('message', 'Component added'); +}); + +it('updates a component', function () { + $component = Component::factory() + ->type('keystone/text') + ->elementId(Entry::factory()->section('pages')->create()->id) + ->create(); + $component->data->merge(['text' => 'foo'])->save(); + + $this->actingAsAdmin() + ->postJson(UrlHelper::actionUrl('keystone/components/update'), [ + 'id' => $component->id, + 'elementId' => $component->elementId, + 'fieldId' => $component->fieldId, + 'fields' => ['text' => 'bar'], + ]) + ->assertOk() + ->assertJsonPath('message', 'Component saved'); + + $component->refresh(); + expect($component->data)->get('text')->toBe('bar'); +}); + +it('deletes a component', function () { + $component = Component::factory() + ->type('keystone/text') + ->elementId(Entry::factory()->section('pages')->create()->id) + ->create(); + + $this->actingAsAdmin() + ->postJson(UrlHelper::actionUrl('keystone/components/delete'), [ + 'id' => $component->id, + 'elementId' => $component->elementId, + 'fieldId' => $component->fieldId, + ]) + ->assertOk() + ->assertJsonPath('message', 'Component deleted'); +}); + +it('moves a component', function () { + $components = Component::factory() + ->type('keystone/section') + ->elementId(Entry::factory()->section('pages')->create()->id) + ->count(2) + ->create(); + + $this->actingAsAdmin() + ->postJson(UrlHelper::actionUrl('keystone/components/move'), [ + 'source' => $components[0]->getQueryCondition(), + 'target' => $components[1]->getQueryCondition(), + 'position' => \markhuot\keystone\enums\MoveComponentPosition::AFTER, + ]) + ->assertOk() + ->assertJsonPath('message', 'Component moved'); +});