diff --git a/src/PrestaShopBundle/Resources/config/services/bundle/repository.yml b/src/PrestaShopBundle/Resources/config/services/bundle/repository.yml index 5d8e557f8733e..a40e3445b4844 100644 --- a/src/PrestaShopBundle/Resources/config/services/bundle/repository.yml +++ b/src/PrestaShopBundle/Resources/config/services/bundle/repository.yml @@ -133,3 +133,9 @@ services: factory: [ '@doctrine.orm.default_entity_manager', getRepository ] arguments: - PrestaShopBundle\Entity\AttributeGroup + + prestashop.core.admin.feature_flag.repository: + class: Doctrine\ORM\EntityRepository + factory: [ '@doctrine.orm.default_entity_manager', getRepository ] + arguments: + - PrestaShopBundle\Entity\FeatureFlag diff --git a/tests/Integration/Core/Form/IdentifiableObject/Handler/FormHandlerChecker.php b/tests/Integration/Core/Form/IdentifiableObject/Handler/FormHandlerChecker.php index a7d753cfbc819..87c917240878d 100644 --- a/tests/Integration/Core/Form/IdentifiableObject/Handler/FormHandlerChecker.php +++ b/tests/Integration/Core/Form/IdentifiableObject/Handler/FormHandlerChecker.php @@ -39,7 +39,7 @@ class FormHandlerChecker implements FormHandlerInterface private $formHandler; /** - * @var int + * @var int|null */ private $lastCreatedId; @@ -53,6 +53,9 @@ public function __construct(FormHandlerInterface $formHandler) $this->formHandler = $formHandler; } + /** + * {@inheritDoc} + */ public function handle(FormInterface $form) { $result = $this->formHandler->handle($form); @@ -61,12 +64,18 @@ public function handle(FormInterface $form) return $result; } + /** + * {@inheritDoc} + */ public function handleFor($id, FormInterface $form) { - return $this->formHandler->handleFor($form, $id); + return $this->formHandler->handleFor($id, $form); } - public function getLastCreatedId(): int + /** + * @return int|null + */ + public function getLastCreatedId(): ?int { return $this->lastCreatedId; } diff --git a/tests/Integration/PrestaShopBundle/Controller/FormFiller/FormChecker.php b/tests/Integration/PrestaShopBundle/Controller/FormFiller/FormChecker.php new file mode 100644 index 0000000000000..0368a1627deec --- /dev/null +++ b/tests/Integration/PrestaShopBundle/Controller/FormFiller/FormChecker.php @@ -0,0 +1,78 @@ + + * @copyright Since 2007 PrestaShop SA and Contributors + * @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0) + */ + +declare(strict_types=1); + +namespace Tests\Integration\PrestaShopBundle\Controller\FormFiller; + +use InvalidArgumentException; +use PHPUnit\Framework\Assert; +use Symfony\Component\DomCrawler\Field\FormField; +use Symfony\Component\DomCrawler\Form; + +class FormChecker +{ + /** + * @param Form $form + * @param array $expectedFormData + * + * @return Form + */ + public function checkForm(Form $form, array $expectedFormData): Form + { + foreach ($expectedFormData as $fieldName => $expectedFormDatum) { + if (!is_array($expectedFormDatum)) { + /** @var FormField $formField */ + $formField = $form->get($fieldName); + $this->assertFormValue($expectedFormDatum, $formField->getValue(), $fieldName); + } else { + throw new InvalidArgumentException('The check for array values has not been implemented yet, your turn!!'); + } + } + + return $form; + } + + /** + * @param mixed $expectedValue + * @param mixed $formValue + * @param string $fieldName + */ + private function assertFormValue($expectedValue, $formValue, string $fieldName): void + { + // We use assertTrue instead of assertEquals because when it fails it raises an error related to Closure + // serialization which makes it very hard to debug (this is because of processIsolation) + Assert::assertTrue( + $expectedValue == $formValue, + sprintf( + 'Invalid value for field %s, expected %s but got %s instead.', + $fieldName, + $expectedValue, + (string) $formValue + ) + ); + } +} diff --git a/tests/Integration/PrestaShopBundle/Controller/FormFiller/FormFiller.php b/tests/Integration/PrestaShopBundle/Controller/FormFiller/FormFiller.php index 4310616371003..61217b71fe2e2 100644 --- a/tests/Integration/PrestaShopBundle/Controller/FormFiller/FormFiller.php +++ b/tests/Integration/PrestaShopBundle/Controller/FormFiller/FormFiller.php @@ -28,8 +28,8 @@ namespace Tests\Integration\PrestaShopBundle\Controller\FormFiller; -use FormField; use Symfony\Component\DomCrawler\Field\ChoiceFormField; +use Symfony\Component\DomCrawler\Field\FormField; use Symfony\Component\DomCrawler\Form; class FormFiller diff --git a/tests/Integration/PrestaShopBundle/Controller/FormGridControllerTestCase.php b/tests/Integration/PrestaShopBundle/Controller/FormGridControllerTestCase.php new file mode 100644 index 0000000000000..ae0547211b32b --- /dev/null +++ b/tests/Integration/PrestaShopBundle/Controller/FormGridControllerTestCase.php @@ -0,0 +1,188 @@ + + * @copyright Since 2007 PrestaShop SA and Contributors + * @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0) + */ + +declare(strict_types=1); + +namespace Tests\Integration\PrestaShopBundle\Controller; + +use Symfony\Component\DomCrawler\Form; +use Tests\Integration\Core\Form\IdentifiableObject\Handler\FormHandlerChecker; +use Tests\Integration\PrestaShopBundle\Controller\FormFiller\FormChecker; + +abstract class FormGridControllerTestCase extends GridControllerTestCase +{ + /** + * @var FormChecker + */ + protected $formChecker; + + /** + * {@inheritDoc} + */ + public function setUp(): void + { + parent::setUp(); + $this->formChecker = new FormChecker(); + } + + /** + * Create an entity by filling the form page with the provided data. You will need to implement these methods: + * + * - generateCreateUrl: returns the creation form url + * - getFormHandlerChecker: return the form handler service which has been encapsulated and allows to get the created ID + * - getCreateSubmitButtonSelector: returns the selector of the button allowing to select the form + * + * @param array $formData + * + * @return int Created entity ID + */ + protected function createEntityFromPage(array $formData): int + { + $createEntityUrl = $this->generateCreateUrl(); + + $this->fillAndSubmitEntityForm($createEntityUrl, $formData, $this->getCreateSubmitButtonSelector()); + $formHandlerChecker = $this->getFormHandlerChecker(); + + $testEntityId = $formHandlerChecker->getLastCreatedId(); + self::assertNotNull($testEntityId); + + return $testEntityId; + } + + /** + * Edit an entity by filling the form page with the provided data. You will need to implement these methods: + * + * - generateEditUrl: returns the edit form url + * - getEditSubmitButtonSelector: returns the selector of the button allowing to select the form + * + * @param array $routeParams + * @param array $formData + */ + protected function editEntityFromPage(array $routeParams, array $formData): void + { + $editEntityUrl = $this->generateEditUrl($routeParams); + + $this->fillAndSubmitEntityForm($editEntityUrl, $formData, $this->getEditSubmitButtonSelector()); + } + + /** + * Parses the entity value from the edit page and assert it matches the expected data. You will need to implement + * these methods: + * + * - generateEditUrl: returns the edit form url + * - getEditSubmitButtonSelector: returns the selector of the button allowing to select the form + * + * @param array $routeParams + * @param array $expectedFormData + */ + protected function assertFormValuesFromPage(array $routeParams, array $expectedFormData): void + { + $editEntityUrl = $this->generateEditUrl($routeParams); + $entityForm = $this->getFormFromPage($editEntityUrl, $this->getEditSubmitButtonSelector()); + $this->formChecker->checkForm($entityForm, $expectedFormData); + } + + /** + * @param string $formUrl + * @param array $formData + * @param string $formButtonSelector + */ + protected function fillAndSubmitEntityForm(string $formUrl, array $formData, string $formButtonSelector = 'submit'): void + { + $filledEntityForm = $this->formFiller->fillForm( + $this->getFormFromPage($formUrl, $formButtonSelector), + $formData + ); + + $this->client->submit($filledEntityForm); + } + + /** + * @param string $formUrl + * @param string $formButtonSelector + * + * @return Form + */ + protected function getFormFromPage(string $formUrl, string $formButtonSelector): Form + { + $crawler = $this->client->request('GET', $formUrl); + $this->assertResponseIsSuccessful(); + + return $this->getFormByButton($crawler, $formButtonSelector); + } + + /** + * @param string $deleteRoute + * @param array $routeParams + */ + protected function deleteEntityFromPage(string $deleteRoute, array $routeParams): void + { + $deleteUrl = $this->router->generate($deleteRoute, $routeParams); + + // Delete url performs the deletion and then redirects to the list + $this->client->request('POST', $deleteUrl); + $this->assertResponseRedirects(); + } + + /** + * Returns the url of the create page. + * + * @return string + */ + abstract protected function generateCreateUrl(): string; + + /** + * Returns the selector allowing to get the create form's submit button. + * + * @return string + */ + abstract protected function getCreateSubmitButtonSelector(): string; + + /** + * In test environment all form handlers are decorated inside a FormHandlerChecker which allows us to get the + * last created ID. This method must be implemented if you want to test your creation form, it returns the form + * handler service used in your creation form. + * + * @return FormHandlerChecker + */ + abstract protected function getFormHandlerChecker(): FormHandlerChecker; + + /** + * Returns the url of the edit page. + * + * @param array $routeParams + * + * @return string + */ + abstract protected function generateEditUrl(array $routeParams): string; + + /** + * Returns the selector allowing to get the edit form's submit button. + * + * @return string + */ + abstract protected function getEditSubmitButtonSelector(): string; +} diff --git a/tests/Integration/PrestaShopBundle/Controller/GridControllerTestCase.php b/tests/Integration/PrestaShopBundle/Controller/GridControllerTestCase.php index aa32c7dc5cf1f..ecf97f35bc97e 100644 --- a/tests/Integration/PrestaShopBundle/Controller/GridControllerTestCase.php +++ b/tests/Integration/PrestaShopBundle/Controller/GridControllerTestCase.php @@ -28,12 +28,12 @@ namespace Tests\Integration\PrestaShopBundle\Controller; -use PrestaShop\PrestaShop\Core\Exception\TypeException; +use InvalidArgumentException; use Symfony\Bundle\FrameworkBundle\Client; -use Symfony\Bundle\FrameworkBundle\Routing\Router; use Symfony\Bundle\FrameworkBundle\Test\WebTestCase; use Symfony\Component\DomCrawler\Crawler; use Symfony\Component\DomCrawler\Form; +use Symfony\Component\Routing\RouterInterface; use Tests\Integration\PrestaShopBundle\Controller\FormFiller\FormFiller; abstract class GridControllerTestCase extends WebTestCase @@ -44,188 +44,108 @@ abstract class GridControllerTestCase extends WebTestCase protected $client; /** - * Route to the grid you are testing - * - * @var string - */ - protected $gridRoute; - - /** - * The id of the test entity with which filters will be tested - * Should be set during SetUp - * - * @var int - */ - protected $testEntityId; - - /** - * The name of entity you are testing, e.g.,address. - * - * @var string - */ - protected $testEntityName; - - /** - * The route to create entity - * - * @var string - */ - protected $createEntityRoute; - - /** - * The route to delete entity - * - * @var string - */ - protected $deleteEntityRoute; - - /** - * Amount of entities in starting list - * - * @var int + * @var RouterInterface */ - protected $initialEntityCount; + protected $router; /** * @var FormFiller */ protected $formFiller; - /** - * Service id form form handler - * - * @var string - */ - protected $formHandlerServiceId; - - /** - * Save button id should always be the same, but can be overridden if needed - * - * @var string - */ - protected $saveButtonId = 'save-button'; - - public function __construct($name = null, array $data = [], $dataName = '') - { - parent::__construct($name, $data, $dataName); - - $this->formFiller = new FormFiller(); - } - - /** - * Creates a test entity and ensures asserts that amount of entities in the list got increased by one - * - * @throws TypeException - */ public function setUp(): void { $this->client = static::createClient(); - $this->client->followRedirects(true); - - /** Asserts that list contains as many entities as expected */ - $crawler = $this->client->request('GET', $this->getIndexRoute($this->client->getKernel()->getContainer()->get('router'))); - $this->assertResponseIsSuccessful(); - - $entities = $this->getEntityList($crawler); - $this->initialEntityCount = $entities->count(); - - $this->createTestEntity(); - - /** Asserts amount of entities in the list increased by one and test entity exists */ - $crawler = $this->client->request('GET', $this->getIndexRoute($this->client->getKernel()->getContainer()->get('router'))); - $entities = $this->getEntityList($crawler); - /* If this fails it means entity was not created correctly */ - self::assertCount($this->initialEntityCount + 1, $entities); - $this->assertTestEntityExists($entities); + $this->router = $this->client->getContainer()->get('router'); + $this->formFiller = new FormFiller(); } /** - * Removes the created test entity and asserts that it was successfully removed from the list. + * Calls the grid page and return the parsed entities it contains, based on the parseEntityFromRow that each + * sub-class must implement. + * + * @param array $routeParams * - * @throws TypeException + * @return TestEntityDTOCollection */ - public function tearDown(): void + protected function getEntitiesFromGrid(array $routeParams = []): TestEntityDTOCollection { - $this->client->followRedirects(true); - $router = $this->client->getContainer()->get('router'); - - /** - * Assumes that deletion route only requires id param and that id has format is $this->testEntityName . 'Id' - * If it's not the case you can always override tearDown with logic specific to grid you are testing - */ - $deleteUrl = $router->generate($this->deleteEntityRoute, [$this->testEntityName . 'Id' => $this->testEntityId]); - $this->client->request('POST', $deleteUrl); + $gridUrl = $this->generateGridUrl($routeParams); + $crawler = $this->client->request('GET', $gridUrl); $this->assertResponseIsSuccessful(); - $crawler = $this->client->request('GET', $this->getIndexRoute($this->client->getKernel()->getContainer()->get('router'))); - - $entities = $this->getEntityList($crawler); - - /* If this fails it means entity deletion did not work as intended */ - self::assertCount($this->initialEntityCount, $entities); + return $this->parseEntitiesFromGridTable($crawler); } /** - * @return void + * Parses all the entities' data from the grid table, based on the parseEntityFromRow that each sub-class must + * implement. + * + * @param Crawler $crawler + * + * @return TestEntityDTOCollection */ - protected function createTestEntity(): void + protected function parseEntitiesFromGridTable(Crawler $crawler): TestEntityDTOCollection { - $router = $this->client->getContainer()->get('router'); - $createEntityUrl = $router->generate($this->createEntityRoute); + $testEntityDTOCollection = new TestEntityDTOCollection(); + $grid = $crawler->filter($this->getGridSelector()); + if (empty($grid->count())) { + throw new InvalidArgumentException(sprintf( + 'Could not find a grid matching CSS selector "%s"', + $this->getGridSelector() + )); + } - $crawler = $this->client->request('GET', $createEntityUrl); - $this->assertResponseIsSuccessful(); + // Get rows but filter the one that is used to indicate there is no result + $entitiesRows = $grid->filter('tbody tr:not(.empty_row)'); - $submitButton = $crawler->selectButton($this->saveButtonId); - /** If you get "InvalidArgumentException: The current node list is empty" error here it means save button was not found */ - $entityForm = $submitButton->form(); + // If no rows are found the collection is empty + if ($entitiesRows->count()) { + $entities = $entitiesRows->each(function ($tr, $i) { + return $this->parseEntityFromRow($tr, $i); + }); - $entityForm = $this->formFiller->fillForm($entityForm, $this->getCreateEntityFormModifications()); + // Fill the collection + foreach ($entities as $entity) { + $testEntityDTOCollection->add($entity); + } + } - /* - * Without changing followRedirects to false when submitting the form - * $dataChecker->getLastCreatedId() returns null. - */ - $this->client->followRedirects(false); - $this->client->submit($entityForm); - $this->client->followRedirects(true); - $formHandlerChecker = $this->client->getContainer()->get($this->formHandlerServiceId); - $this->testEntityId = $formHandlerChecker->getLastCreatedId(); - self::assertNotNull($this->testEntityId); + return $testEntityDTOCollection; } /** - * If this test fails it's likely problem with filters being incorrect or filtering not working - * Asserts that there is only one entity left in the list after using filters + * Calls the grid page with specific filters and return the parsed entities it contains, based on the + * parseEntityFromRow that each sub-class must implement. * * @param array $testFilters + * @param array $routeParams * - * @throws TypeException + * @return TestEntityDTOCollection */ - protected function assertFiltersFindOnlyTestEntity(array $testFilters): void + protected function getFilteredEntitiesFromGrid(array $testFilters, array $routeParams = []): TestEntityDTOCollection { - $crawler = $this->client->request('GET', $this->getIndexRoute($this->client->getKernel()->getContainer()->get('router'))); + $gridUrl = $this->generateGridUrl($routeParams); + $crawler = $this->client->request('GET', $gridUrl); $this->assertResponseIsSuccessful(); + $gridRoute = $this->client->getRequest()->attributes->get('_route'); - /** Assert that list contains all entities and thus not affected by anything */ - $entities = $this->getEntityList($crawler); - self::assertCount($this->initialEntityCount + 1, $entities); - - /** - * Submit filters - */ $filterForm = $this->fillFiltersForm($crawler, $testFilters); - $this->client->followRedirects(true); - $crawler = $this->client->submit($filterForm); + // Filter url applies the search filter and then redirects to the grid + $this->client->submit($filterForm); + $this->assertResponseRedirects(); - /** - * Assert that there is only test entity left in the list after using filters - */ - $entities = $this->getEntityList($crawler); + // Then we manually request the url that was used as redirection, and finally return the parsed entities + $redirectUrl = $this->client->getResponse()->headers->get('Location'); + $crawler = $this->client->request('GET', $redirectUrl); + $this->assertResponseIsSuccessful(); + + // We check that the redirection happened successfully to the same route + $redirectionRoute = $this->client->getRequest()->attributes->get('_route'); + $this->assertEquals($gridRoute, $redirectionRoute); - self::assertCount(1, $entities); - $this->assertTestEntityExists($entities); + return $this->parseEntitiesFromGridTable($crawler); } /** @@ -236,61 +156,77 @@ protected function assertFiltersFindOnlyTestEntity(array $testFilters): void */ protected function fillFiltersForm(Crawler $crawler, array $formModifications): Form { - $button = $crawler->selectButton($this->testEntityName . '[actions][search]'); - $filtersForm = $button->form(); + $filtersForm = $this->getFormByButton($crawler, $this->getFilterSearchButtonSelector()); $this->formFiller->fillForm($filtersForm, $formModifications); return $filtersForm; } /** - * Asserts test entity exists with the list + * @param Crawler $crawler + * @param string $formButtonSelector * - * @param TestEntityDTOCollection $entities + * @return Form */ - protected function assertTestEntityExists(TestEntityDTOCollection $entities): void + protected function getFormByButton(Crawler $crawler, string $formButtonSelector): Form { - $ids = array_map(function ($entity) { - return $entity->getId(); - }, iterator_to_array($entities)); - self::assertContains($this->getTestEntity()->getId(), $ids); + $submitButton = $crawler->selectButton($formButtonSelector); + try { + $form = $submitButton->form(); + } catch (InvalidArgumentException $e) { + throw new InvalidArgumentException(sprintf( + 'Could not find form in the page, maybe the button selector "%s" is not adapted, usually you can use the button id (without the #) or its name', + $formButtonSelector + ), $e->getCode(), $e); + } + + return $form; } /** - * @param Crawler $crawler + * Asserts collection contains the entity matching the provided ID * - * @return TestEntityDTOCollection - * - * @throws TypeException + * @param TestEntityDTOCollection $entities + * @param int $searchEntityId */ - protected function getEntityList(Crawler $crawler): TestEntityDTOCollection + protected function assertCollectionContainsEntity(TestEntityDTOCollection $entities, int $searchEntityId): void { - $testEntityDTOCollection = new TestEntityDTOCollection(); - $entities = $crawler->filter('#' . $this->testEntityName . '_grid_table')->filter('tbody tr')->each(function ($tr, $i) { - return $this->getEntity($tr, $i); - }); - foreach ($entities as $entity) { - $testEntityDTOCollection->add($entity); - } + $ids = array_map(function ($entity) { + return $entity->getId(); + }, iterator_to_array($entities)); - return $testEntityDTOCollection; + $this->assertContains($searchEntityId, $ids); } /** - * Can be overridden if you need to manipulate route in in any way - * - * @param Router $router + * Returns the selector allowing to get the grid's search button. * * @return string */ - protected function getIndexRoute(Router $router): string - { - return $router->generate($this->gridRoute); - } + abstract protected function getFilterSearchButtonSelector(): string; - abstract protected function getTestEntity(): TestEntityDTO; + /** + * @param array $routeParams + * + * @return string + */ + abstract protected function generateGridUrl(array $routeParams = []): string; - abstract protected function getCreateEntityFormModifications(): array; + /** + * Returns the selector of the tested grid, for example: #products_grid_table + * + * @return string + */ + abstract protected function getGridSelector(): string; - abstract protected function getEntity(Crawler $tr, int $i): TestEntityDTO; + /** + * This method parse a row from the grid and returns a TestEntityDTO which contains, at the minimum, the ID of the + * entity plus additional variables that you could wish to test. + * + * @param Crawler $tr + * @param int $i + * + * @return TestEntityDTO + */ + abstract protected function parseEntityFromRow(Crawler $tr, int $i): TestEntityDTO; } diff --git a/tests/Integration/PrestaShopBundle/Controller/Sell/Catalog/ProductControllerTest.php b/tests/Integration/PrestaShopBundle/Controller/Sell/Catalog/ProductControllerTest.php index 653481d4f035a..275ea22a40775 100644 --- a/tests/Integration/PrestaShopBundle/Controller/Sell/Catalog/ProductControllerTest.php +++ b/tests/Integration/PrestaShopBundle/Controller/Sell/Catalog/ProductControllerTest.php @@ -28,35 +28,32 @@ namespace Tests\Integration\PrestaShopBundle\Controller\Sell\Catalog; -use PrestaShop\PrestaShop\Core\Exception\TypeException; use PrestaShop\PrestaShop\Core\FeatureFlag\FeatureFlagSettings; -use Symfony\Bundle\FrameworkBundle\Routing\Router; use Symfony\Component\DomCrawler\Crawler; -use Tests\Integration\PrestaShopBundle\Controller\GridControllerTestCase; +use Tests\Integration\Core\Form\IdentifiableObject\Handler\FormHandlerChecker; +use Tests\Integration\PrestaShopBundle\Controller\FormGridControllerTestCase; use Tests\Integration\PrestaShopBundle\Controller\TestEntityDTO; -class ProductControllerTest extends GridControllerTestCase +class ProductControllerTest extends FormGridControllerTestCase { + private const TEST_CREATION_NAME = 'testCreationProductName'; + private const TEST_CREATION_QUANTITY = 456; + private const TEST_CREATION_PRICE = 3.14; + + private const TEST_UPDATED_NAME = 'testProductName'; + private const TEST_UPDATED_QUANTITY = 987; + private const TEST_UPDATED_PRICE = 87.7; + /** * @var bool */ private $changedProductFeatureFlag = false; - public function __construct($name = null, array $data = [], $dataName = '') - { - parent::__construct($name, $data, $dataName); - - $this->createEntityRoute = 'admin_products_v2_create'; - $this->testEntityName = 'product'; - $this->deleteEntityRoute = 'admin_products_v2_delete'; - $this->formHandlerServiceId = 'prestashop.core.form.identifiable_object.product_form_handler'; - $this->saveButtonId = 'product_footer_save'; - } - public function setUp(): void { - $this->client = static::createClient(); - $productFeatureFlag = $this->client->getContainer()->get('doctrine.orm.entity_manager')->getRepository('PrestaShopBundle:FeatureFlag')->findOneBy(['name' => FeatureFlagSettings::FEATURE_FLAG_PRODUCT_PAGE_V2]); + parent::setUp(); + $featureFlagRepository = $this->client->getContainer()->get('prestashop.core.admin.feature_flag.repository'); + $productFeatureFlag = $featureFlagRepository->findOneBy(['name' => FeatureFlagSettings::FEATURE_FLAG_PRODUCT_PAGE_V2]); if (!$productFeatureFlag->isEnabled()) { $featureFlagModifier = $this->client->getContainer()->get('prestashop.core.feature_flags.modifier'); $featureFlagModifier->updateConfiguration( @@ -66,13 +63,10 @@ public function setUp(): void ); $this->changedProductFeatureFlag = true; } - - parent::setUp(); } public function tearDown(): void { - parent::tearDown(); if ($this->changedProductFeatureFlag) { $featureFlagModifier = $this->client->getContainer()->get('prestashop.core.feature_flags.modifier'); $featureFlagModifier->updateConfiguration( @@ -81,78 +75,201 @@ public function tearDown(): void ] ); } + + // Call parent tear down later or the kernel will be shut down + parent::tearDown(); } - protected function getIndexRoute(Router $router): string + public function testIndex(): int { - /* Asserts amount of entities in the list increased by one and test entity exists */ - return $router->generate('admin_products_v2_index', - [ - '_route' => 0, - 'product[offset]' => 0, - 'product[limit]' => 100, - ] + $products = $this->getEntitiesFromGrid(); + $this->assertNotEmpty($products); + + return $products->count(); + } + + /** + * @depends testIndex + * + * @param int $initialEntityCount + * + * @return int + */ + public function testCreate(int $initialEntityCount): int + { + // First create product + $formData = [ + 'product[header][name][1]' => static::TEST_CREATION_NAME, + 'product[stock][quantities][quantity][delta]' => static::TEST_CREATION_QUANTITY, + 'product[pricing][retail_price][price_tax_excluded]' => static::TEST_CREATION_PRICE, + ]; + $createdProductId = $this->createEntityFromPage($formData); + $this->assertNotNull($createdProductId); + + // Check that there is one more product in the list + $newProducts = $this->getEntitiesFromGrid(); + $this->assertCount($initialEntityCount + 1, $newProducts); + $this->assertCollectionContainsEntity($newProducts, $createdProductId); + + // Check that the product was correctly set with the expected type (the format in the form is not the same) + $expectedFormData = [ + 'product[header][name][1]' => static::TEST_CREATION_NAME, + 'product[stock][quantities][quantity][quantity]' => static::TEST_CREATION_QUANTITY, + 'product[pricing][retail_price][price_tax_excluded]' => static::TEST_CREATION_PRICE, + ]; + $this->assertFormValuesFromPage( + ['productId' => $createdProductId], + $expectedFormData ); + + return $createdProductId; } /** - * Tests all provided entity filters - * All filters are tested in one test make tests run faster + * @depends testCreate + * + * @param int $productId * - * @throws TypeException + * @return int */ - public function testProductFilters(): void + public function testEdit(int $productId): int { - foreach ($this->getTestFilters() as $testFilter) { - $this->assertFiltersFindOnlyTestEntity($testFilter); - } + // First update the product with a few data + $formData = [ + 'product[header][name][1]' => static::TEST_UPDATED_NAME, + 'product[stock][quantities][quantity][delta]' => static::TEST_UPDATED_QUANTITY, + 'product[pricing][retail_price][price_tax_excluded]' => static::TEST_UPDATED_PRICE, + ]; + + $this->editEntityFromPage(['productId' => $productId], $formData); + + // Then check that it was correctly updated + // Price is reformatted with 6 digits + $expectedFormData = [ + 'product[header][name][1]' => static::TEST_UPDATED_NAME, + 'product[stock][quantities][quantity][quantity]' => static::TEST_UPDATED_QUANTITY + static::TEST_CREATION_QUANTITY, + 'product[pricing][retail_price][price_tax_excluded]' => static::TEST_UPDATED_PRICE, + ]; + $this->assertFormValuesFromPage( + ['productId' => $productId], + $expectedFormData + ); + + return $productId; } /** - * @return array + * @depends testEdit + * + * @param int $productId + * + * @return int */ - protected function getTestFilters(): array + public function testFilters(int $productId): int { - /* @todo add rest of the tests, need for product form to be complete */ - return [ - ['product[name]' => 'stProd'], + // These are filters which are only supposed to match the created product, they might need to be updated + // in case the data from fixtures change and match these test data. + $gridFilters = [ + [ + 'product[name]' => static::TEST_UPDATED_NAME, + ], [ - 'product[id_product][min_field]' => $this->getTestEntity()->getId(), - 'product[id_product][max_field]' => $this->getTestEntity()->getId(), + 'product[id_product][min_field]' => $productId, + 'product[id_product][max_field]' => $productId, ], [ - 'product[quantity][min_field]' => $this->getTestEntity()->quantity, - 'product[quantity][max_field]' => $this->getTestEntity()->quantity, + 'product[quantity][min_field]' => static::TEST_CREATION_QUANTITY + static::TEST_UPDATED_QUANTITY, + 'product[quantity][max_field]' => static::TEST_CREATION_QUANTITY + static::TEST_UPDATED_QUANTITY, ], [ - 'product[price_tax_excluded][min_field]' => $this->getTestEntity()->price, - 'product[price_tax_excluded][max_field]' => $this->getTestEntity()->price, + 'product[price_tax_excluded][min_field]' => static::TEST_UPDATED_PRICE, + 'product[price_tax_excluded][max_field]' => static::TEST_UPDATED_PRICE, ], ]; + + foreach ($gridFilters as $testFilter) { + $products = $this->getFilteredEntitiesFromGrid($testFilter); + $this->assertGreaterThanOrEqual(1, count($products), sprintf( + 'Expected at least one product with filters %s', + var_export($testFilter, true) + )); + $this->assertCollectionContainsEntity($products, $productId); + } + + return $productId; } /** - * @return TestEntityDTO + * @depends testFilters + * + * @param int $productId */ - protected function getTestEntity(): TestEntityDTO + public function testDelete(int $productId): void { - return new TestEntityDTO( - $this->testEntityId, - [ - 'name' => 'testProductName', - 'quantity' => 987, - 'price' => '87,7', - ] - ); + $products = $this->getEntitiesFromGrid(); + $initialEntityCount = $products->count(); + + $this->deleteEntityFromPage('admin_products_v2_delete', ['productId' => $productId]); + + $newProducts = $this->getEntitiesFromGrid(); + $this->assertCount($initialEntityCount - 1, $newProducts); } /** - * @param $tr - * @param $i - * - * @return TestEntityDTO + * {@inheritDoc} + */ + protected function getFilterSearchButtonSelector(): string + { + return 'product[actions][search]'; + } + + /** + * {@inheritDoc} + */ + protected function getCreateSubmitButtonSelector(): string + { + return 'product_footer_save'; + } + + /** + * {@inheritDoc} + */ + protected function getEditSubmitButtonSelector(): string + { + return 'product_footer_save'; + } + + /** + * {@inheritDoc} + */ + protected function generateCreateUrl(): string + { + return $this->router->generate('admin_products_v2_create'); + } + + /** + * {@inheritDoc} */ - protected function getEntity(Crawler $tr, int $i): TestEntityDTO + protected function generateEditUrl(array $routeParams): string + { + return $this->router->generate('admin_products_v2_edit', $routeParams); + } + + /** + * {@inheritDoc} + */ + protected function getFormHandlerChecker(): FormHandlerChecker + { + /** @var FormHandlerChecker $checker */ + $checker = $this->client->getContainer()->get('prestashop.core.form.identifiable_object.product_form_handler'); + + return $checker; + } + + /** + * {@inheritDoc} + */ + protected function parseEntityFromRow(Crawler $tr, int $i): TestEntityDTO { return new TestEntityDTO( (int) trim($tr->filter('.column-id_product')->text()), @@ -162,23 +279,25 @@ protected function getEntity(Crawler $tr, int $i): TestEntityDTO } /** - * Gets modifications that are needed to fill address form - * - * @return array + * {@inheritDoc} */ - protected function getCreateEntityFormModifications(): array - { - /** @todo add rest of the tests, need for product form to be complete */ - $testEntity = $this->getTestEntity(); - - return [ - 'product[header][name][1]' => $testEntity->name, - // 'product[shortcuts][stock][quantity]' => $testEntity->quantity, - 'product[stock][quantities][quantity][delta]' => $testEntity->quantity, - 'product[shipping][additional_shipping_cost]' => 0, - 'product[pricing][retail_price][price_tax_excluded]' => $testEntity->price, - // 'product[shortcuts][retail_price][price_tax_excluded]' => $testEntity->price, - // 'product[shortcuts][retail_price][price_tax_included]' => $testEntity->price, - ]; + protected function generateGridUrl(array $routeParams = []): string + { + if (empty($routeParams)) { + $routeParams = [ + 'product[offset]' => 0, + 'product[limit]' => 100, + ]; + } + + return $this->router->generate('admin_products_v2_index', $routeParams); + } + + /** + * {@inheritDoc} + */ + protected function getGridSelector(): string + { + return '#product_grid_table'; } } diff --git a/tests/Integration/PrestaShopBundle/Controller/Sell/Customer/Address/AddressControllerTest.php b/tests/Integration/PrestaShopBundle/Controller/Sell/Customer/Address/AddressControllerTest.php index 86b59852694a0..ea058786719ef 100644 --- a/tests/Integration/PrestaShopBundle/Controller/Sell/Customer/Address/AddressControllerTest.php +++ b/tests/Integration/PrestaShopBundle/Controller/Sell/Customer/Address/AddressControllerTest.php @@ -29,97 +29,195 @@ namespace Tests\Integration\PrestaShopBundle\Controller\Sell\Customer\Address; use Country; -use PrestaShop\PrestaShop\Core\Exception\TypeException; use Symfony\Component\DomCrawler\Crawler; -use Tests\Integration\PrestaShopBundle\Controller\GridControllerTestCase; +use Tests\Integration\Core\Form\IdentifiableObject\Handler\FormHandlerChecker; +use Tests\Integration\PrestaShopBundle\Controller\FormGridControllerTestCase; use Tests\Integration\PrestaShopBundle\Controller\TestEntityDTO; -class AddressControllerTest extends GridControllerTestCase +class AddressControllerTest extends FormGridControllerTestCase { private $countryId; - public function __construct($name = null, array $data = [], $dataName = '') + private $backupCountry; + + private $legacyContext; + + /** + * {@inheritDoc} + */ + public function setUp(): void + { + parent::setUp(); + + // We get the country ID for Lithuania, and we set this country in the context so the controller will + // generate a form adapted to this country (especially regarding states selector) + $this->legacyContext = $this->client->getContainer()->get('prestashop.adapter.legacy.context'); + $this->backupCountry = $this->legacyContext->getContext()->country; + + $this->countryId = Country::getByIso('LT'); + $this->legacyContext->getContext()->country = new Country($this->countryId); + } + + /** + * {@inheritDoc} + */ + protected function tearDown(): void + { + parent::tearDown(); + $this->legacyContext->getContext()->country = $this->backupCountry; + } + + public function testIndex(): int { - parent::__construct($name, $data, $dataName); + $adresses = $this->getEntitiesFromGrid(); + $this->assertNotEmpty($adresses); - $this->createEntityRoute = 'admin_addresses_create'; - $this->gridRoute = 'admin_addresses_index'; - $this->testEntityName = 'address'; - $this->deleteEntityRoute = 'admin_addresses_delete'; - $this->formHandlerServiceId = 'prestashop.core.form.identifiable_object.handler.address_form_handler'; + return $adresses->count(); } /** - * Tests all provided entity filters - * All filters are tested in one test make tests run faster + * @depends testIndex * - * @throws TypeException + * @param int $initialEntityCount + * + * @return int */ - public function testAddressFilters(): void + public function testCreate(int $initialEntityCount): int { - foreach ($this->getTestFilters() as $testFilter) { - $this->assertFiltersFindOnlyTestEntity($testFilter); - } + // First create address + $formData = [ + 'customer_address[customer_email]' => 'pub@prestashop.com', + 'customer_address[alias]' => 'create_alias', + 'customer_address[first_name]' => 'createfirstname', + 'customer_address[last_name]' => 'createlastname', + 'customer_address[address1]' => 'createaddress1', + 'customer_address[postcode]' => '11111', + 'customer_address[city]' => 'createcity', + 'customer_address[id_country]' => $this->countryId, + ]; + $addressId = $this->createEntityFromPage($formData); + + // Check that there is one more address in the list + $newAddresses = $this->getEntitiesFromGrid(); + $this->assertCount($initialEntityCount + 1, $newAddresses); + $this->assertCollectionContainsEntity($newAddresses, $addressId); + + // Email is only used for initial association, but not editable after + unset($formData['customer_address[customer_email]']); + $this->assertFormValuesFromPage( + ['addressId' => $addressId], + $formData + ); + + return $addressId; + } + + /** + * @depends testCreate + * + * @param int $addressId + * + * @return int + */ + public function testEdit(int $addressId): int + { + // First update the address with a few data + $formData = [ + 'customer_address[alias]' => 'edit_alias', + 'customer_address[first_name]' => 'editfirstname', + 'customer_address[last_name]' => 'editlastname', + 'customer_address[address1]' => 'editaddress1', + 'customer_address[postcode]' => '11111', + 'customer_address[city]' => 'editcity', + 'customer_address[id_country]' => $this->countryId, + ]; + + $this->editEntityFromPage(['addressId' => $addressId], $formData); + + // Then check that it was correctly updated + $this->assertFormValuesFromPage( + ['addressId' => $addressId], + $formData + ); + + return $addressId; } /** - * @return array + * @depends testEdit + * + * @param int $addressId + * + * @return int */ - protected function getTestFilters(): array + public function testFilters(int $addressId): int { - return [ - ['address[id_address]' => $this->getTestEntity()->getId()], - ['address[firstname]' => 'firstname'], - ['address[lastname]' => 'testLa'], - ['address[address1]' => 'address1'], + $gridFilters = [ + ['address[id_address]' => $addressId], + ['address[firstname]' => 'editfirstname'], + ['address[lastname]' => 'editlastname'], + ['address[address1]' => 'editaddress1'], ['address[postcode]' => '11111'], - ['address[city]' => 'stcity'], + ['address[city]' => 'editcity'], ['address[id_country]' => $this->countryId], ]; + + foreach ($gridFilters as $testFilter) { + $addresses = $this->getFilteredEntitiesFromGrid($testFilter); + $this->assertGreaterThanOrEqual(1, count($addresses), sprintf( + 'Expected at least one address with filters %s', + var_export($testFilter, true) + )); + $this->assertCollectionContainsEntity($addresses, $addressId); + } + + return $addressId; } /** - * @return TestEntityDTO + * @depends testFilters + * + * @param int $addressId */ - protected function getTestEntity(): TestEntityDTO + public function testDelete(int $addressId): void { - return new TestEntityDTO( - $this->testEntityId, - [ - 'firstName' => 'testfirstname', - 'lastName' => 'testlastname', - 'address' => 'testaddress1', - 'postCode' => '11111', - 'city' => 'testcity', - 'country' => 'lithuania', - ] - ); + $addresses = $this->getEntitiesFromGrid(); + $initialEntityCount = $addresses->count(); + + $this->deleteEntityFromPage('admin_addresses_delete', ['addressId' => $addressId]); + + $newAddresses = $this->getEntitiesFromGrid(); + $this->assertCount($initialEntityCount - 1, $newAddresses); } /** - * @return void + * {@inheritDoc} */ - protected function createTestEntity(): void + protected function generateGridUrl(array $routeParams = []): string { - // We get the country ID for Lithuania, and we set this country in the context so the controller will - // generate a form adapted to this country (especially regarding states selector) - $this->countryId = Country::getByIso('LT'); - $legacyContext = $this->client->getContainer()->get('prestashop.adapter.legacy.context'); - $backupCountry = $legacyContext->getContext()->country; - $legacyContext->getContext()->country = new Country($this->countryId); + return $this->router->generate('admin_addresses_index', $routeParams); + } - parent::createTestEntity(); - // We can now reset the original context country - $legacyContext->getContext()->country = $backupCountry; + /** + * {@inheritDoc} + */ + protected function getGridSelector(): string + { + return '#address_grid_table'; } /** - * @param $tr - * @param $i - * - * @return TestEntityDTO + * {@inheritDoc} */ - protected function getEntity(Crawler $tr, int $i): TestEntityDTO + protected function getFilterSearchButtonSelector(): string + { + return 'address[actions][search]'; + } + + /** + * {@inheritDoc} + */ + protected function parseEntityFromRow(Crawler $tr, int $i): TestEntityDTO { return new TestEntityDTO( (int) trim($tr->filter('.column-id_address')->text()), @@ -135,23 +233,45 @@ protected function getEntity(Crawler $tr, int $i): TestEntityDTO } /** - * Gets modifications that are needed to fill address form - * - * @return array + * {@inheritDoc} + */ + protected function generateCreateUrl(): string + { + return $this->router->generate('admin_addresses_create'); + } + + /** + * {@inheritDoc} */ - protected function getCreateEntityFormModifications(): array + protected function getCreateSubmitButtonSelector(): string { - $testEntity = $this->getTestEntity(); + return 'save-button'; + } - return [ - 'customer_address[customer_email]' => 'pub@prestashop.com', - 'customer_address[alias]' => 'test_alias', - 'customer_address[first_name]' => $testEntity->firstName, - 'customer_address[last_name]' => $testEntity->lastName, - 'customer_address[address1]' => $testEntity->address, - 'customer_address[postcode]' => $testEntity->postCode, - 'customer_address[city]' => $testEntity->city, - 'customer_address[id_country]' => $this->countryId, - ]; + /** + * {@inheritDoc} + */ + protected function getFormHandlerChecker(): FormHandlerChecker + { + /** @var FormHandlerChecker $checker */ + $checker = $this->client->getContainer()->get('prestashop.core.form.identifiable_object.handler.address_form_handler'); + + return $checker; + } + + /** + * {@inheritDoc} + */ + protected function generateEditUrl(array $routeParams): string + { + return $this->router->generate('admin_addresses_edit', $routeParams); + } + + /** + * {@inheritDoc} + */ + protected function getEditSubmitButtonSelector(): string + { + return 'save-button'; } }