From 6c08e8160f90f4cd34d613d65e041bd470d7a0b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20Vo=C5=99=C3=AD=C5=A1ek?= Date: Mon, 8 May 2023 21:55:41 +0200 Subject: [PATCH] Improve table column filter coverage (#2038) --- demos/_unit-test/scroll.php | 2 +- demos/collection/tablecolumnmenu.php | 5 +- demos/collection/tablecolumns.php | 2 +- demos/interactive/loader2.php | 2 +- src/Behat/Context.php | 40 ++++++------ src/Form/Control/Checkbox.php | 4 -- src/Grid.php | 24 -------- src/Table/Column/FilterModel.php | 9 --- src/Table/Column/FilterPopup.php | 2 +- src/View.php | 5 +- tests-behat/filter.feature | 25 +++++++- tests-behat/grid.feature | 3 + tests/TableColumnTest.php | 18 ++++++ tests/ViewTest.php | 91 +++++++++++++++++++++++++++- 14 files changed, 164 insertions(+), 68 deletions(-) create mode 100644 tests/TableColumnTest.php diff --git a/demos/_unit-test/scroll.php b/demos/_unit-test/scroll.php index f74b47bc96..370e01a6b9 100644 --- a/demos/_unit-test/scroll.php +++ b/demos/_unit-test/scroll.php @@ -16,7 +16,7 @@ $grid->setModel($model); $makeClickJsToastFx = function (string $source) use ($grid) { - return new JsToast(['message' => new JsExpression('[] + [] + []', [$source, ' clicked: ', $grid->table->jsRow()->data('id')])]); + return new JsToast(['message' => new JsExpression('[] + [] + []', [$source, ' clicked: ', $grid->jsRow()->data('id')])]); }; $grid->addActionButton(['icon' => 'bell'], $makeClickJsToastFx('action')); diff --git a/demos/collection/tablecolumnmenu.php b/demos/collection/tablecolumnmenu.php index f2182cf738..da84e2ab57 100644 --- a/demos/collection/tablecolumnmenu.php +++ b/demos/collection/tablecolumnmenu.php @@ -6,6 +6,7 @@ use Atk4\Ui\Grid; use Atk4\Ui\Header; +use Atk4\Ui\Js\JsToast; use Atk4\Ui\Table; use Atk4\Ui\Text; use Atk4\Ui\View; @@ -40,9 +41,9 @@ Text::addTo($pop)->set('This popup is loaded dynamically'); }); -// Another dropdown menu. +// another dropdown menu $colTitle->addDropdown(['Change', 'Reorder', 'Update'], function (string $item) { - return 'Title item: ' . $item; + return new JsToast(['message' => 'Title item: ' . $item]); }); // ----------------------------------------------------------------------------- diff --git a/demos/collection/tablecolumns.php b/demos/collection/tablecolumns.php index 0787ae3a60..8b8648e0ad 100644 --- a/demos/collection/tablecolumns.php +++ b/demos/collection/tablecolumns.php @@ -125,7 +125,7 @@ protected function init(): void 'value_not_always_present' => random_int(0, 100) > 50 ? 'have value' : '', 'interests' => '1st label, 2nd label', 'rating' => random_int(100, 300) / 100, - 'note' => 'lorem ipsum lorem dorem lorem', + 'note' => $id !== 3 ? 'lorem ipsum lorem dorem lorem' : null, ]); } diff --git a/demos/interactive/loader2.php b/demos/interactive/loader2.php index c17fde99fb..0e755bcb66 100644 --- a/demos/interactive/loader2.php +++ b/demos/interactive/loader2.php @@ -26,7 +26,7 @@ $countryLoader = Loader::addTo($c->addColumn(), ['loadEvent' => false, 'shim' => [Text::class, 'Select country on your left']]); -$grid->table->onRowClick($countryLoader->jsLoad(['id' => $grid->table->jsRow()->data('id')])); +$grid->table->onRowClick($countryLoader->jsLoad(['id' => $grid->jsRow()->data('id')])); $countryLoader->set(function (Loader $p) { Form::addTo($p)->setModel((new Country($p->getApp()->db))->load($_GET['id'])); diff --git a/src/Behat/Context.php b/src/Behat/Context.php index 184db86209..14067c460c 100644 --- a/src/Behat/Context.php +++ b/src/Behat/Context.php @@ -99,16 +99,16 @@ protected function getFinishedScript(): string /** * Wait till jQuery AJAX request finished and no animation is perform. */ - protected function jqueryWait(string $extraWaitCondition = 'true', int $maxWaitdurationMs = 5000): void + protected function jqueryWait(string $extraWaitCondition = 'true', array $args = [], int $maxWaitdurationMs = 5000): void { $finishedScript = '(' . $this->getFinishedScript() . ') && (' . $extraWaitCondition . ')'; $s = microtime(true); $c = 0; while (microtime(true) - $s <= $maxWaitdurationMs / 1000) { - $this->getSession()->wait($maxWaitdurationMs, $finishedScript); + $this->getSession()->wait($maxWaitdurationMs, $finishedScript, $args); usleep(10_000); - if ($this->getSession()->evaluateScript($finishedScript)) { + if ($this->getSession()->evaluateScript($finishedScript, $args)) { // TODO wait() uses evaluateScript(), dedup if (++$c >= 2) { return; } @@ -313,7 +313,7 @@ public function iPressMenuButton(string $btnLabel, string $selector): void { $menu = $this->findElement(null, $selector); $link = $this->findElement($menu, '//a[text()="' . $btnLabel . '"]'); - $this->getSession()->executeScript('$(\'#' . $link->getAttribute('id') . '\').click()'); + $link->click(); } /** @@ -353,7 +353,7 @@ public function iClickLink(string $label): void public function iClickUsingSelector(string $selector): void { $element = $this->findElement(null, $selector); - $this->getSession()->executeScript('$(arguments[0]).click()', [$element]); + $element->click(); } /** @@ -361,7 +361,8 @@ public function iClickUsingSelector(string $selector): void */ public function iClickPaginatorPage(string $pageNumber): void { - $this->getSession()->executeScript('$(\'a.item[data-page=' . $pageNumber . ']\').click()'); + $element = $this->findElement(null, 'a.item[data-page="' . $pageNumber . '"]'); + $element->click(); } /** @@ -418,7 +419,9 @@ public function iFillModalField(string $fieldName, string $value): void */ public function iClickCloseModal(): void { - $this->getSession()->executeScript('$(\'.modal.visible.active.front > i.icon.close\')[0].click()'); + $modal = $this->findElement(null, '.modal.visible.active.front'); + $closeIcon = $this->findElement($modal, '//i.icon.close'); + $closeIcon->click(); } /** @@ -483,8 +486,7 @@ public function iClickTabWithTitle(string $tabTitle): void { $tabMenu = $this->findElement(null, '.ui.tabular.menu'); $link = $this->findElement($tabMenu, '//a[text()="' . $tabTitle . '"]'); - - $this->getSession()->executeScript('$(\'#' . $link->getAttribute('id') . '\').click()'); + $link->click(); } /** @@ -540,24 +542,23 @@ public function iSearchGridFor(string $text): void */ public function iSelectValueInLookup(string $value, string $inputName): void { + $isSelectorXpath = $this->parseSelector($inputName)[0] === 'xpath'; + // get dropdown item from Fomantic-UI which is direct parent of input html element - $lookupElem = $this->findElement(null, '//input[@name="' . $inputName . '"]/parent::div'); + $lookupElem = $this->findElement(null, ($isSelectorXpath ? $inputName : '//input[@name="' . $inputName . '"]') . '/parent::div'); // open dropdown and wait till fully opened (just a click is not triggering it) - $this->getSession()->executeScript('$(\'#' . $lookupElem->getAttribute('id') . '\').dropdown(\'show\')'); - $this->jqueryWait('$(\'#' . $lookupElem->getAttribute('id') . '\').hasClass(\'visible\')'); + $this->getSession()->executeScript('$(arguments[0]).dropdown(\'show\')', [$lookupElem]); + $this->jqueryWait('$(arguments[0]).hasClass(\'visible\')', [$lookupElem]); // select value $valueElem = $this->findElement($lookupElem, '//div[text()="' . $value . '"]'); - $this->getSession()->executeScript('$(\'#' . $lookupElem->getAttribute('id') . '\').dropdown(\'set selected\', ' . $valueElem->getAttribute('data-value') . ');'); + $this->getSession()->executeScript('$(arguments[0]).dropdown(\'set selected\', arguments[1]);', [$lookupElem, $valueElem->getAttribute('data-value')]); $this->jqueryWait(); // hide dropdown and wait till fully closed - $this->getSession()->executeScript('$(\'#' . $lookupElem->getAttribute('id') . '\').dropdown(\'hide\');'); - $this->jqueryWait(); - // for unknown reasons, dropdown very often remains visible in CI, so hide twice - $this->getSession()->executeScript('$(\'#' . $lookupElem->getAttribute('id') . '\').dropdown(\'hide\');'); - $this->jqueryWait('!$(\'#' . $lookupElem->getAttribute('id') . '\').hasClass(\'visible\')'); + $this->getSession()->executeScript('$(arguments[0]).dropdown(\'hide\');', [$lookupElem]); + $this->jqueryWait('!$(arguments[0]).hasClass(\'visible\')', [$lookupElem]); } /** @@ -710,8 +711,7 @@ public function iClickFilterColumnName(string $columnName): void { $column = $this->findElement(null, "th[data-column='" . $columnName . "']"); $icon = $this->findElement($column, 'i'); - - $this->getSession()->executeScript('$(\'#' . $icon->getAttribute('id') . '\').click()'); + $icon->click(); } /** diff --git a/src/Form/Control/Checkbox.php b/src/Form/Control/Checkbox.php index 518581a37d..93e789ecdd 100644 --- a/src/Form/Control/Checkbox.php +++ b/src/Form/Control/Checkbox.php @@ -25,10 +25,6 @@ class Checkbox extends Form\Control public function __construct($label = []) { - if (func_num_args() > 1) { // prevent bad usage - throw new \Error('Too many method arguments'); - } - parent::__construct($label); $this->label = $this->content; diff --git a/src/Grid.php b/src/Grid.php index 3b3e830b5a..79236f2a58 100644 --- a/src/Grid.php +++ b/src/Grid.php @@ -140,26 +140,6 @@ protected function initTable(): Table return $table; } - /** - * Set Table\Column\Actions seed. - * - * @param array $seed - */ - public function setActionDecorator($seed): void - { - $this->actionButtonsDecorator = $seed; - } - - /** - * Set Table\Column\ActionMenu seed. - * - * @param array $seed - */ - public function setActionMenuDecorator($seed): void - { - $this->actionMenuDecorator = $seed; - } - /** * Add new column to grid. If column with this name already exists, * an. Simply calls Table::addColumn(), so check that method out. @@ -450,10 +430,6 @@ private function getActionMenu() */ public function addActionMenuFromModel(string $appliesTo = null): void { - if (!$this->model) { - throw new Exception('Model not set, set it prior to add item'); - } - foreach ($this->model->getUserActions($appliesTo) as $action) { $this->addActionMenuItem($action); } diff --git a/src/Table/Column/FilterModel.php b/src/Table/Column/FilterModel.php index c10dc0bc16..2d7622c202 100644 --- a/src/Table/Column/FilterModel.php +++ b/src/Table/Column/FilterModel.php @@ -96,9 +96,6 @@ protected function init(): void $this->afterInit(); } - /** - * Perform further initialization. - */ public function afterInit(): void { $this->addField('name', ['default' => $this->lookupField->shortName, 'system' => true]); @@ -116,9 +113,6 @@ public function afterInit(): void }); } - /** - * Recall filter model data. - */ public function recallData(): ?array { return $this->recall('data'); @@ -142,9 +136,6 @@ public function getFormDisplayRules(): array return []; } - /** - * Check if this model is using session or not. - */ public function clearData(): void { $this->forget(); diff --git a/src/Table/Column/FilterPopup.php b/src/Table/Column/FilterPopup.php index 961d054f10..e4f7294921 100644 --- a/src/Table/Column/FilterPopup.php +++ b/src/Table/Column/FilterPopup.php @@ -51,7 +51,7 @@ protected function init(): void $this->form = Form::addTo($this)->addClass(''); $this->form->buttonSave->addClass(''); - $this->form->addGroup('Where ' . $this->field->getCaption() . ' :'); + $this->form->addGroup('Where ' . $this->field->getCaption() . ':'); $this->form->buttonSave->set('Set'); diff --git a/src/View.php b/src/View.php index 6de70f363e..1b8de22670 100644 --- a/src/View.php +++ b/src/View.php @@ -7,6 +7,7 @@ use Atk4\Data\Model; use Atk4\Data\Persistence; use Atk4\Ui\Js\Jquery; +use Atk4\Ui\Js\JsBlock; use Atk4\Ui\Js\JsChain; use Atk4\Ui\Js\JsExpression; use Atk4\Ui\Js\JsExpressionable; @@ -1122,11 +1123,11 @@ public function getJsRenderActions(): string $actions = []; foreach ($this->_jsActions as $eventActions) { foreach ($eventActions as $action) { - $actions[] = $action->jsRender(); + $actions[] = $action; } } - return implode('; ', $actions); + return (new JsBlock($actions))->jsRender(); } /** diff --git a/tests-behat/filter.feature b/tests-behat/filter.feature index 52621a70e4..e2621cc96f 100644 --- a/tests-behat/filter.feature +++ b/tests-behat/filter.feature @@ -2,10 +2,33 @@ Feature: Table Filter Scenario: Given I am on "collection/tablefilter.php" - Then I should see "Clear Filters" + Then I should see "Australia" Then I click filter column name "atk_fp_country__name" When I fill in "value" with "united kingdom" Then I press button "Set" + Then I should not see "Australia" + Then I should see "United Kingdom" + Then I click filter column name "atk_fp_country__phonecode" + When I fill field using "//div.popup[5]//input[@name='value']" with "44" + When I click using selector "//div.popup[5]//div[text()='Set']" + Then I should see "United Kingdom" + Then I click filter column name "atk_fp_country__phonecode" + When I fill field using "//div.popup[5]//input[@name='value']" with "4" + When I click using selector "//div.popup[5]//div[text()='Set']" + Then I should not see "United Kingdom" + Then I should see "No records" + Then I click filter column name "atk_fp_country__phonecode" + When I click using selector "//div.popup[5]//div[text()='Clear']" + Then I should not see "No records" + Then I should see "United Kingdom" + Then I click filter column name "is_uk" + Then I select value "Is No" in lookup "//div.popup[6]//input[@name='op']" + When I click using selector "//div.popup[6]//div[text()='Set']" + Then I should see "No records" + Then I click filter column name "is_uk" + Then I select value "Is Yes" in lookup "//div.popup[6]//input[@name='op']" + When I click using selector "//div.popup[6]//div[text()='Set']" Then I should see "United Kingdom" Then I press menu button "Clear Filters" using selector ".ui.menu.atk-grid-menu" + Then I should not see "United Kingdom" Then I should see "Australia" diff --git a/tests-behat/grid.feature b/tests-behat/grid.feature index f34950a000..c2bdb6df2a 100644 --- a/tests-behat/grid.feature +++ b/tests-behat/grid.feature @@ -45,3 +45,6 @@ Feature: Grid Then I should not see "This popup is loaded dynamically" When I click using selector "(//th//div.atk-table-dropdown)[2]/i" Then I should see "This popup is loaded dynamically" + When I click using selector "(//th//div.atk-table-dropdown)[3]/div.dropdown" + When I click using selector "(//th//div.atk-table-dropdown)[3]/div.dropdown/div.menu/div.item[2]" + Then Toast display should contain text "Title item: Reorder" diff --git a/tests/TableColumnTest.php b/tests/TableColumnTest.php new file mode 100644 index 0000000000..954363e57e --- /dev/null +++ b/tests/TableColumnTest.php @@ -0,0 +1,18 @@ +expectException(\Error::class); + $this->expectExceptionMessage('Too many method arguments'); + new Column([], []); + } +} diff --git a/tests/ViewTest.php b/tests/ViewTest.php index 4e676db5b2..b69284a9f0 100644 --- a/tests/ViewTest.php +++ b/tests/ViewTest.php @@ -5,6 +5,8 @@ namespace Atk4\Ui\Tests; use Atk4\Core\Phpunit\TestCase; +use Atk4\Data\Model; +use Atk4\Ui\AbstractView; use Atk4\Ui\Exception; use Atk4\Ui\View; @@ -23,7 +25,7 @@ public function testMultipleTimesRender(): void static::assertSame($a, $b); } - public function testAddAfterRender(): void + public function testAddAfterRenderException(): void { $v = new View(); $v->set('foo'); @@ -32,7 +34,7 @@ public function testAddAfterRender(): void $v->render(); $this->expectException(Exception::class); - View::addTo($v); // no adding after rendering + View::addTo($v); } public function testVoidTagRender(): void @@ -46,4 +48,89 @@ public function testVoidTagRender(): void $v->setApp($this->createApp()); static::assertSame('', $v->render()); } + + public function testAddDelayedInit(): void + { + $v = new View(); + $vInner = new View(); + + $v->add($vInner); + static::assertFalse($v->isInitialized()); + static::assertFalse($vInner->isInitialized()); + + $vLayout = new View(); + $vLayout->setApp($this->createApp()); + $vLayout->add($v); + + static::assertTrue($v->isInitialized()); + static::assertTrue($vInner->isInitialized()); + } + + public function testAddDelayedAbstractViewInit(): void + { + $v = new class() extends AbstractView { }; + $vInner = new View(); + + $v->add($vInner); + static::assertFalse($v->isInitialized()); + static::assertFalse($vInner->isInitialized()); + + $vLayout = new View(); + $vLayout->setApp($this->createApp()); + $vLayout->add($v); + + static::assertTrue($v->isInitialized()); + static::assertTrue($vInner->isInitialized()); + } + + public function testTooManyArgumentsConstructorError(): void + { + $this->expectException(\Error::class); + $this->expectExceptionMessage('Too many method arguments'); + new View([], []); + } + + public function testTooManyArgumentsAddError(): void + { + $v = new View(); + $vInner = new View(); + + $this->expectException(\Error::class); + $this->expectExceptionMessage('Too many method arguments'); + $v->add($vInner, [], []); + } + + public function testTooManyArgumentsAbstractViewAddError(): void + { + $v = new class() extends AbstractView { }; + $vInner = new View(); + + $this->expectException(\Error::class); + $this->expectExceptionMessage('Too many method arguments'); + $v->add($vInner, [], []); + } + + public function testSetModelTwiceException(): void + { + $v = new View(); + $m1 = new Model(); + $m2 = new Model(); + $v->setModel($m1); + + $this->expectException(Exception::class); + $this->expectExceptionMessage('Different model is already set'); + $v->setModel($m2); + } + + public function testSetSourceZeroKeyException(): void + { + $v = new View(); + $v->setSource(['a', 'b']); + + $v = new View(); + + $this->expectException(Exception::class); + $this->expectExceptionMessage('Source data contains unsupported zero key'); + $v->setSource(['a', 2 => 'b']); + } }