Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,9 +97,10 @@ for the mega menu.

### Behat

47 scenarios under `features/`, one suite per feature (`tests/Behat/Resources/suites.php`). 42 use
the Mink `symfony` session (no browser); 5 are tagged `@javascript` and drive a real Chrome: the
icon picker, the media rows added in the taxon form and the conditions (preview, save, refusal).
48 scenarios under `features/`, one suite per feature (`tests/Behat/Resources/suites.php`). 42 use
the Mink `symfony` session (no browser); 6 are tagged `@javascript` and drive a real Chrome: the
icon picker, the media rows added in the taxon form, the conditions (preview, save, refusal) and
the mobile menu drawer.

The [Docker stack](#docker-stack) brings both of the following. On the host, as configured in
`behat.dist.php`:
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -141,8 +141,8 @@ full list and the support policy:

**Tested** by 198 PHPUnit tests (units; integration tests on a real database for every condition
type and operator, the facets, the grid pagination and the synchronization; functional tests of the
back office) and 47 Behat scenarios, 5 of them in headless Chrome (icon picker, media rows,
conditions preview, save and refusal). Both suites pass on Sylius 2.1, 2.2 and 2.3; the PHPUnit suite
back office) and 48 Behat scenarios, 6 of them in headless Chrome (icon picker, media rows,
conditions preview, save and refusal, mobile menu). Both suites pass on Sylius 2.1, 2.2 and 2.3; the PHPUnit suite
also on PostgreSQL 17.

The continuous integration matrix covers Sylius 2.1, 2.2 and 2.3, PHP 8.3, 8.4 and 8.5, Symfony 7.4
Expand Down
19 changes: 6 additions & 13 deletions assets/shop/js/mobile-menu-controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ import { Controller } from '@hotwired/stimulus';
* Stimulus controller for the mobile mega menu drill-down drawer.
*
* Usage: add data-controller="at-mobile-menu" on the drawer element.
* The burger button (#at-mn-burger) can be anywhere in the DOM; the controller
* finds it by ID and binds to it using a capture-phase listener so Bootstrap
* Offcanvas event handlers cannot intercept the click first.
* The burger button (#at-mn-burger) sits outside the drawer, out of reach of a
* data-action: the controller finds it by ID and listens to its clicks. The click
* keeps propagating, so analytics listening on the document still see it.
*
* Data attributes on inner elements:
* data-action="at-mobile-menu#goTo" data-at-mobile-menu-goto-param="[screen-id]"
Expand All @@ -17,15 +17,14 @@ export default class extends Controller {

initialize() {
this._stack = ['at-mn-screen-root'];
this._openHandler = this._handleBurgerClick.bind(this);
this._openHandler = this.open.bind(this);
this._keyHandler = this._handleKey.bind(this);
}

connect() {
const burger = document.getElementById('at-mn-burger');
if (burger) {
// Capture phase: fires before any bubbling Bootstrap handlers.
burger.addEventListener('click', this._openHandler, true);
burger.addEventListener('click', this._openHandler);
}
document.addEventListener('keydown', this._keyHandler);
this._syncScreens();
Expand All @@ -34,7 +33,7 @@ export default class extends Controller {
disconnect() {
const burger = document.getElementById('at-mn-burger');
if (burger) {
burger.removeEventListener('click', this._openHandler, true);
burger.removeEventListener('click', this._openHandler);
}
document.removeEventListener('keydown', this._keyHandler);
}
Expand Down Expand Up @@ -105,12 +104,6 @@ export default class extends Controller {
this._focusActiveScreen();
}

_handleBurgerClick(event) {
// Prevent Bootstrap (or any other handler) from processing this click.
event.stopImmediatePropagation();
this.open();
}

_handleKey(event) {
if (!this.element.classList.contains('at-mn-drawer--open')) { return; }

Expand Down
2 changes: 1 addition & 1 deletion docs/testing/tests.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ product`, `this channel`). The shop templates carry `data-test-*` attributes
| `shop/browsing_taxon_pages` | Featured child taxons and products, media zones (top, left, bottom, slider mode), main image, media cards in the list, at the start or among the products, and in the list view, name with color and icon |
| `shop/filtering_products_with_advanced_filters` | Filter values and counts, filtering, filters kept by the grid search, filters only when enabled |
| `shop/browsing_universe_pages` | Universe title, child taxons instead of a product list, featured products of the children |
| `shop/navigating_with_the_mega_menu` | Mega menu per channel, featured categories, mobile menu button, color in the menu |
| `shop/navigating_with_the_mega_menu` | Mega menu per channel, featured categories, mobile menu button, color in the menu, mobile drawer opened from the burger, drilled into and closed with Escape (`@javascript`) |
| `taxon/materializing_conditional_taxons` | Conditions added, edited and removed, incomplete condition (fail closed), renaming, disabled products, deletion, command |

```bash
Expand Down
10 changes: 10 additions & 0 deletions features/shop/navigating_with_the_mega_menu.feature
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,13 @@ Feature: Navigating with the mega menu
And the "Watches" taxon hides its color and icon in the menu
When I open the shop homepage
Then the "Watches" taxon should be shown without its color in the menu

@ui @javascript
Scenario: Browsing the mega menu on a small screen
Given this channel has the mega menu enabled
When I open the shop homepage on a small screen
And I open the mobile menu
And I open the "Watches" taxon in the mobile menu
Then the mobile menu should link to the "Classic" taxon
When I close the mobile menu with the Escape key
Then the mobile menu should be closed
44 changes: 44 additions & 0 deletions tests/Behat/Context/Ui/Shop/BrowsingAdvancedTaxonContext.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
namespace Tests\CylleneDigital\SyliusAdvancedTaxonPlugin\Behat\Context\Ui\Shop;

use Behat\Behat\Context\Context;
use Behat\Hook\AfterScenario;
use Behat\Step\Then;
use Behat\Step\When;
use FriendsOfBehat\PageObjectExtension\Page\PageInterface;
Expand Down Expand Up @@ -44,6 +45,37 @@ public function iOpenTheShopHomepage(): void
$this->homePage->open(['_locale' => 'en_US']);
}

#[When('I open the shop homepage on a small screen')]
public function iOpenTheShopHomepageOnASmallScreen(): void
{
$this->homePage->open(['_locale' => 'en_US']);
$this->menu->useSmallScreen();
}

#[When('I open the mobile menu')]
public function iOpenTheMobileMenu(): void
{
$this->menu->openMobileMenu();
}

#[When('I open the :taxon taxon in the mobile menu')]
public function iOpenTheTaxonInTheMobileMenu(string $taxon): void
{
$this->menu->openTaxonInMobileMenu($taxon);
}

#[When('I close the mobile menu with the Escape key')]
public function iCloseTheMobileMenuWithTheEscapeKey(): void
{
$this->menu->closeMobileMenuWithEscape();
}

#[AfterScenario('@javascript')]
public function restoreTheScreen(): void
{
$this->menu->restoreScreen();
}

#[When('I filter the products by :value in the :filter filter')]
public function iFilterTheProductsBy(string $value, string $filter): void
{
Expand Down Expand Up @@ -220,4 +252,16 @@ public function theTaxonShouldBeShownWithoutItsColorInTheMenu(TaxonInterface $ta
{
Assert::null($this->menu->getMenuItemColor((string) $taxon->getName()));
}

#[Then('the mobile menu should link to the :taxon taxon')]
public function theMobileMenuShouldLinkToTheTaxon(string $taxon): void
{
Assert::true($this->menu->isTaxonLinkedInMobileMenu($taxon));
}

#[Then('the mobile menu should be closed')]
public function theMobileMenuShouldBeClosed(): void
{
Assert::false($this->menu->isMobileMenuOpen());
}
}
66 changes: 66 additions & 0 deletions tests/Behat/Element/Shop/MenuElement.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,21 @@
namespace Tests\CylleneDigital\SyliusAdvancedTaxonPlugin\Behat\Element\Shop;

use FriendsOfBehat\PageObjectExtension\Element\Element;
use Webmozart\Assert\Assert;

/**
* The shop main menu, as the plugin renders it: the mega menu when the channel enables it, the
* Sylius menu otherwise.
*/
final class MenuElement extends Element
{
private const BROWSER_TIMEOUT_MS = 5000;

/** @var array{0: int, 1: int}|null */
private ?array $desktopSize = null;

private const OPEN_DRAWER = 'document.querySelector("#at-mn-drawer")?.classList.contains("at-mn-drawer--open")';

public function isMegaMenuDisplayed(): bool
{
return $this->hasElement('mega_menu');
Expand All @@ -22,6 +30,58 @@ public function hasMobileMenuButton(): bool
return $this->hasElement('mobile_menu_button');
}

/**
* Below the large breakpoint the menu is the burger and its drawer.
*/
public function useSmallScreen(): void
{
/** @var array{0: int, 1: int} $size */
$size = $this->getSession()->evaluateScript('[window.outerWidth, window.outerHeight]');
$this->desktopSize = $size;
$this->getSession()->resizeWindow(390, 844);
}

public function restoreScreen(): void
{
if ($this->desktopSize !== null) {
$this->getSession()->resizeWindow(...$this->desktopSize);
$this->desktopSize = null;
}
}

public function openMobileMenu(): void
{
// A real click: the drawer has to open from the burger itself.
$this->getElement('mobile_menu_button')->click();
$this->waitFor(self::OPEN_DRAWER);
}

public function openTaxonInMobileMenu(string $taxon): void
{
$this->getElement('mobile_menu_active_screen')->pressButton($taxon);
$this->waitFor(sprintf(
'document.querySelector(%s)?.textContent.includes(%s)',
json_encode('#at-mn-drawer [data-mn-state="active"] .at-mn-screen-title'),
json_encode($taxon),
));
}

public function isTaxonLinkedInMobileMenu(string $taxon): bool
{
return $this->getElement('mobile_menu_active_screen')->hasLink($taxon);
}

public function closeMobileMenuWithEscape(): void
{
$this->getSession()->executeScript('document.dispatchEvent(new KeyboardEvent("keydown", {key: "Escape", bubbles: true}))');
$this->waitFor('!' . self::OPEN_DRAWER);
}

public function isMobileMenuOpen(): bool
{
return (bool) $this->getSession()->evaluateScript(self::OPEN_DRAWER);
}

public function isFeaturedInMegaMenu(string $taxon, string $featuredTaxon): bool
{
return $this->hasElement('mega_menu_featured_item', ['%taxon%' => $taxon, '%featured%' => $featuredTaxon]);
Expand All @@ -43,7 +103,13 @@ protected function getDefinedElements(): array
'mega_menu' => '[data-test-mega-menu]',
'mega_menu_featured_item' => '[data-test-mega-menu-featured="%taxon%"] [data-test-menu-item="%featured%"]',
'menu_item' => '[data-test-menu-item="%taxon%"]',
'mobile_menu_active_screen' => '#at-mn-drawer [data-mn-state="active"]',
'mobile_menu_button' => '[data-test-mobile-menu-button]',
];
}

private function waitFor(string $condition): void
{
Assert::true($this->getSession()->wait(self::BROWSER_TIMEOUT_MS, $condition), sprintf('Timed out waiting for: %s', $condition));
}
}
Loading