contacts_events-8.x-1.x-dev/tests/src/FunctionalJavascript/BookingTicketsTest.php
tests/src/FunctionalJavascript/BookingTicketsTest.php
<?php
namespace Drupal\Tests\contacts_events\FunctionalJavascript;
use Drupal\commerce_order\Entity\Order;
use Drupal\commerce_store\Entity\Store;
use Drupal\contacts_events\Entity\Event;
use Drupal\contacts_events\Entity\EventClass;
use Drupal\contacts_events\Entity\EventInterface;
use Drupal\contacts_events\Plugin\Commerce\CheckoutFlow\BookingFlow;
use Drupal\Core\Datetime\DrupalDateTime;
use Drupal\Core\Url;
use Drupal\datetime\Plugin\Field\FieldType\DateTimeItemInterface;
use Drupal\FunctionalJavascriptTests\WebDriverTestBase;
/**
* Test the tickets step of the booking process.
*
* @group contacts_events
*/
class BookingTicketsTest extends WebDriverTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = ['contacts_events'];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'classy';
/**
* {@inheritdoc}
*
* @todo Remove bookkeeping once
* https://www.drupal.org/project/drupal/issues/3044920 is resolved.
*/
protected $strictConfigSchema = FALSE;
/**
* The event.
*
* @var \Drupal\contacts_events\Entity\EventInterface
*/
protected $event;
/**
* A user with permission to the checkout.
*
* @var \Drupal\user\UserInterface
*/
protected $user;
/**
* The booking.
*
* @var \Drupal\commerce_order\Entity\OrderInterface
*/
protected $booking;
/**
* The next screenshot number for the filename.
*
* @var int
*/
protected static $screenshotCount = 0;
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
// Set up another event class.
$class = EventClass::create([
'id' => 'child',
'label' => 'Child',
'type' => 'contacts_ticket',
'selectable' => FALSE,
'max_age' => 17,
]);
$class->save();
// Set up the event.
$this->event = Event::create(['type' => 'default']);
$start = DrupalDateTime::createFromTimestamp(strtotime('+2 months'))
->setTime(10, 0, 0);
$end = (clone $start)
->add(new \DateInterval('P1D'))
->setTime(17, 0, 0);
$this->event
->set('title', 'Test event')
->set('code', 'TEST')
->set('date', [
'value' => $start->format(DateTimeItemInterface::DATETIME_STORAGE_FORMAT),
'end_value' => $end->format(DateTimeItemInterface::DATETIME_STORAGE_FORMAT),
])
->set('booking_status', EventInterface::STATUS_OPEN)
->set('booking_windows', [
[
'id' => 'standard',
'label' => 'Standard',
],
])
->set('ticket_classes', ['child', 'standard'])
->set('ticket_price', [
[
'number' => 4.99,
'currency_code' => 'USD',
'booking_window' => 'standard',
'class' => 'child',
],
[
'number' => 9.99,
'currency_code' => 'USD',
'booking_window' => 'standard',
'class' => 'standard',
],
])
->save();
$this->user = $this->drupalCreateUser([
'can book for contacts_events',
]);
$this->drupalLogin($this->user);
$name = $this->randomMachineName(8);
$mail = $this->user->getEmail();
$currency_importer = \Drupal::service('commerce_price.currency_importer');
$currency_importer->import('USD');
$store = Store::create([
'type' => 'online',
'uid' => 1,
'name' => $name,
'mail' => $mail,
'address' => [
'country_code' => 'US',
'address_line1' => $this->randomString(),
'locality' => $this->randomString(5),
'administrative_area' => 'WI',
'postal_code' => '53597',
],
'default_currency' => 'USD',
'billing_countries' => [
'US',
],
]);
$store->save();
// Create the booking.
$this->booking = Order::create([
'type' => 'contacts_booking',
'store_id' => $store->id(),
'event' => $this->event,
'uid' => $this->user->id(),
'checkout_step' => 'tickets',
]);
$this->booking->save();
}
/**
* Tests that adding a ticket to an empty booking works.
*/
public function testAddTicketToEmpty() {
// Load up the booking page.
$this->drupalGet(Url::fromRoute(BookingFlow::ROUTE_NAME, [
'commerce_order' => $this->booking->id(),
'step' => 'tickets',
]));
// Check the page loaded correctly.
$assert_session = $this->assertSession();
$assert_session->elementExists('css', 'form.commerce-checkout-flow-booking-flow');
$assert_session->elementExists('css', 'div.checkout-pane-tickets');
// Fill our our first ticket and submit it.
$this->assertTicketFormEmpty(0);
$data = [
'First name' => 'John',
'Surname' => 'Smith',
// Chromedriver in different environments varies between US and UK date
// formats for entry into the HTML 5 widget, so we work round that by
// forcing a the first of Jan.
'date_of_birth' => (new DrupalDateTime('-16 years'))->format('01/01/Y'),
];
$this->fillTicket($data, ['child' => 'Child'], '$4.99', 0, FALSE);
$this->submitTicket('Create ticket', 'John Smith', '$4.99');
// Edit the first ticket to adjust the date of birth.
$this->click('input[type=submit][value="Edit"][data-drupal-selector="edit-tickets-order-items-entities-0-actions-ief-entity-edit"]');
$assert_session->assertWaitOnAjaxRequest();
$this->fillTicket(['date_of_birth' => '01/01/1995'], ['standard' => 'Standard'], '$9.99', 0, TRUE);
$this->screenshotOutput();
$this->submitTicket('Update ticket', 'John Smith', '$9.99');
$this->screenshotOutput();
// Add a second ticket.
$this->click('input[type=submit][value="Add new ticket"]');
$assert_session->assertWaitOnAjaxRequest();
$this->screenshotOutput();
$this->assertTicketFormEmpty(1);
// Fill out and submit the form.
$data = [
'First name' => 'Jane',
'Surname' => 'Smith',
'date_of_birth' => '01/01/1998',
];
$this->fillTicket($data, ['standard' => 'Standard'], '$9.99', 1, FALSE);
$this->submitTicket('Create ticket', 'Jane Smith', '$9.99', 1);
// Edit and resave the second ticket.
$this->click('input[type=submit][value="Edit"][data-drupal-selector="edit-tickets-order-items-entities-1-actions-ief-entity-edit"]');
$assert_session->assertWaitOnAjaxRequest();
$this->fillTicket(['First name' => 'Janet'], NULL, NULL, 1, TRUE);
$this->submitTicket('Update ticket', 'Janet Smith', '$9.99', 1);
}
/**
* Helper for asserting a ticket form is empty.
*
* @param int $delta
* Index of new enity.
*/
protected function assertTicketFormEmpty($delta = 0) {
$locators = [
0 => 'First name',
1 => 'Surname',
2 => 'Email',
"tickets[order_items][form][$delta][purchased_entity][0][inline_entity_form][date_of_birth][0][value][date]" => 'Date of birth',
];
$page = $this->getSession()->getPage();
foreach ($locators as $locator => $title) {
if (is_int($locator)) {
$locator = $title;
}
$this->assertEmpty($page->findField($locator)->getValue(), "{$title} should be empty; was {$page->findField($locator)->getValue()}");
}
}
/**
* Fill out the ticket, optionally checking the price update.
*
* @param array $data
* An array of data to submit.
* @param array|null $expected_classes
* If we are checking the expected classes, an array of value/label pairs.
* @param string|null $expected_price
* If w eare checking the expected price, a formatted price.
* @param int|null $delta
* The delta of the ticket being edited, or NULL for a new ticket.
* @param bool $editing
* Whether editing an existing item (true) or creating a new one (false).
*
* @throws \Behat\Mink\Exception\ElementNotFoundException
* @throws \Behat\Mink\Exception\ElementTextException
*/
protected function fillTicket(array $data, array $expected_classes = NULL, $expected_price = NULL, $delta = 0, $editing = FALSE) {
// Fill out the general details.
$page = $this->getSession()->getPage();
foreach ($data as $locator => $value) {
if ($locator == 'date_of_birth') {
if (!$editing) {
$locator = "tickets[order_items][form][$delta][purchased_entity][0][inline_entity_form][date_of_birth][0][value][date]";
}
else {
$locator = "tickets[order_items][form][inline_entity_form][entities][{$delta}][form][purchased_entity][0][inline_entity_form][date_of_birth][0][value][date]";
}
}
$page->fillField($locator, $value);
}
// If we don't have expected classes/price, return now.
if (!isset($expected_classes) && !isset($expected_price)) {
return;
}
// Wait for the AJAX response to complete (allowing for a delayed trigger).
usleep(600000);
$this->assertSession()->assertWaitOnAjaxRequest();
$this->screenshotOutput();
// Check the expected class options.
if (isset($expected_classes)) {
$class_element = $page->findField('Class');
$options = [];
foreach ($class_element->findAll('css', 'option') as $option) {
/** @var \Behat\Mink\Element\NodeElement $option */
$options[$option->getAttribute('value')] = $option->getText();
}
$this->assertEquals($expected_classes, $options, 'Correct classes on update.');
}
// Check the expected price.
if (isset($expected_price)) {
if (!$editing) {
$selector = ".form-item-tickets-order-items-form-{$delta}-purchased-entity-0-inline-entity-form-mapped-price-0-price-final";
}
else {
$selector = ".form-item-tickets-order-items-form-inline-entity-form-entities-{$delta}-form-purchased-entity-0-inline-entity-form-mapped-price-0-price-final";
}
$this->assertSession()->elementTextContains('css', $selector, $expected_price);
}
}
/**
* Submit a ticket edit form.
*
* @param string $button_text
* The text on the button to press.
* @param string $title
* The expected title on the IEF row.
* @param string $price
* The expected formatted price on the IEF row.
* @param int $delta
* The delta of the row we're checking.
*/
protected function submitTicket($button_text, $title, $price, $delta = 0) {
$row_number = $delta + 1;
$this->click('input[type=submit][value="' . $button_text . '"]');
$assert_session = $this->assertSession();
$assert_session->assertWaitOnAjaxRequest();
// Check there are no errors.
$assert_session->elementNotExists('css', '.alert-danger');
// Check the title and price.
$row = $this->getSession()
->getPage()
->find('css', 'table.ief-entity-table')
->find('css', "tbody tr:nth-child({$row_number})");
$this->assertEquals($title, $row->find('css', '.inline-entity-form-commerce_order_item-label')->getText(), 'Correct title');
$this->assertEquals($price, $row->find('css', '.inline-entity-form-commerce_order_item-unit_price')->getText(), 'Correct price');
}
/**
* Capture a screenshot of the current page and create a HTML output page.
*
* @param string $name
* The filename suffix and alt text for the screenshot.
*/
protected function screenshotOutput($name = NULL) {
if (!$this->htmlOutputEnabled) {
return;
}
// If we were given a name, use that as a label and filename identifier.
$output = '';
if ($name) {
$output .= "<h4>{$name}</h4>";
$filename = $this->htmlOutputClassName . '-' . $this->htmlOutputCounter . '-' . $this->htmlOutputTestId . '-' . $name . '.jpg';
}
else {
$filename = $this->htmlOutputClassName . '-' . $this->htmlOutputCounter . '-' . $this->htmlOutputTestId . '.jpg';
}
$this->createScreenshot($this->htmlOutputDirectory . '/' . $filename);
$this->htmlOutput($output . '<img src="' . $filename . '"/>');
}
}
