This commit is contained in:
2024-09-01 18:54:23 +05:00
parent 76d18365a5
commit 061f09eca1
1597 changed files with 109451 additions and 1 deletions

View File

@@ -0,0 +1,79 @@
<?php
namespace Laravel\Nova\Testing\Browser\Components;
use Laravel\Dusk\Browser;
class ActionDropdownComponent extends Component
{
/**
* Get the root selector for the component.
*
* @return string
*/
public function selector()
{
return 'div[data-menu-open="true"]';
}
/**
* Run the action with the given URI key.
*
* @param \Laravel\Dusk\Browser $browser
* @param string $uriKey
* @return void
*/
public function runWithConfirmation(Browser $browser, string $uriKey)
{
$browser->click("button[data-action-id='{$uriKey}']")
->elsewhereWhenAvailable(new Modals\ConfirmActionModalComponent(), function ($browser) {
$browser->confirm();
});
}
/**
* Run the action with the given URI key.
*
* @param \Laravel\Dusk\Browser $browser
* @param string $uriKey
* @return void
*/
public function runWithoutConfirmation(Browser $browser, string $uriKey)
{
$browser->click("button[data-action-id='{$uriKey}']")
->elsewhere('', function ($browser) {
$browser->assertDontSee('@cancel-action-button');
});
}
/**
* Open the action modal but cancel the action.
*
* @param \Laravel\Dusk\Browser $browser
* @param string $uriKey
* @param callable $fieldCallback
* @return void
*/
public function select(Browser $browser, string $uriKey, $fieldCallback)
{
$browser->click("button[data-action-id='{$uriKey}']")
->elsewhereWhenAvailable(new Modals\ConfirmActionModalComponent(), function ($browser) use ($fieldCallback) {
$fieldCallback($browser);
});
}
/**
* Open the action modal but cancel the action.
*
* @param \Laravel\Dusk\Browser $browser
* @param string $uriKey
* @return void
*/
public function cancel(Browser $browser, string $uriKey)
{
$browser->click("button[data-action-id='{$uriKey}']")
->elsewhereWhenAvailable(new Modals\ConfirmActionModalComponent(), function ($browser) {
$browser->cancel();
});
}
}

View File

@@ -0,0 +1,42 @@
<?php
namespace Laravel\Nova\Testing\Browser\Components;
use Laravel\Dusk\Browser;
class BreadcrumbComponent extends Component
{
/**
* Get the root selector for the component.
*
* @return string
*/
public function selector()
{
return 'nav[aria-label="breadcrumb"]';
}
/**
* Assert current page match the title.
*
* @param \Laravel\Dusk\Browser $browser
* @param string $title
* @return void
*/
public function assertCurrentPageTitle(Browser $browser, string $title)
{
$browser->assertSeeIn('@current-page', $title);
}
/**
* Get the element shortcuts for the page.
*
* @return array
*/
public function elements()
{
return [
'@current-page' => 'li[aria-current="page"]',
];
}
}

View File

@@ -0,0 +1,11 @@
<?php
namespace Laravel\Nova\Testing\Browser\Components;
use Laravel\Dusk\Component as BaseComponent;
use Laravel\Nova\Testing\Browser\Concerns\InteractsWithElements;
abstract class Component extends BaseComponent
{
use InteractsWithElements;
}

View File

@@ -0,0 +1,16 @@
<?php
namespace Laravel\Nova\Testing\Browser\Components\Controls;
class RelationSelectControlComponent extends SelectControlComponent
{
/**
* Get the root selector associated with this component.
*
* @return string
*/
public function selector()
{
return "@{$this->attribute}-select";
}
}

View File

@@ -0,0 +1,31 @@
<?php
namespace Laravel\Nova\Testing\Browser\Components\Controls;
use Laravel\Nova\Testing\Browser\Components\Component;
class SelectControlComponent extends Component
{
public $attribute;
/**
* Create a new component instance.
*
* @param string $attribute
* @return void
*/
public function __construct(string $attribute)
{
$this->attribute = $attribute;
}
/**
* Get the root selector associated with this component.
*
* @return string
*/
public function selector()
{
return "select[dusk='{$this->attribute}']";
}
}

View File

@@ -0,0 +1,75 @@
<?php
namespace Laravel\Nova\Testing\Browser\Components;
use Laravel\Dusk\Browser;
class DetailComponent extends Component
{
public $resourceName;
public $resourceId;
/**
* Create a new component instance.
*
* @param string $resourceName
* @param int $resourceId
* @return void
*/
public function __construct($resourceName, $resourceId)
{
$this->resourceName = $resourceName;
$this->resourceId = $resourceId;
}
/**
* Open the delete selector.
*
* @param \Laravel\Dusk\Browser $browser
* @return void
*
* @throws \Facebook\WebDriver\Exception\TimeOutException
*/
public function openControlSelector(Browser $browser)
{
$browser->whenAvailable("@{$this->resourceId}-control-selector", function ($browser) {
$browser->click('');
})->pause(100);
}
/**
* Get the root selector for the component.
*
* @return string
*/
public function selector()
{
return '@'.$this->resourceName.'-detail-component';
}
/**
* Assert that the browser page contains the component.
*
* @param \Laravel\Dusk\Browser $browser
* @return void
*/
public function assert(Browser $browser)
{
tap($this->selector(), function ($selector) use ($browser) {
$browser->pause(100)
->waitFor($selector)
->assertVisible($selector);
});
}
/**
* Get the element shortcuts for the component.
*
* @return array
*/
public function elements()
{
return [];
}
}

View File

@@ -0,0 +1,69 @@
<?php
namespace Laravel\Nova\Testing\Browser\Components;
use Laravel\Dusk\Browser;
use Laravel\Nova\Testing\Browser\Concerns\InteractsWithInlineCreateRelation;
class FormComponent extends Component
{
use InteractsWithInlineCreateRelation;
protected $selector;
protected $formUniqueId;
/**
* Create a new component instance.
*
* @param string|null $selector
* @return void
*/
public function __construct($selector = null)
{
$this->selector = $selector;
}
/**
* Get the root selector for the component.
*
* @return string
*/
public function selector()
{
return $this->selector ?? '#app [dusk="content"] form:not([dusk="form-button"])';
}
/**
* Set a field's value using JavaScript.
*
* @param \Laravel\Dusk\Browser $browser
* @param string $attribute
* @param mixed $value
* @return void
*/
public function setFieldValue(Browser $browser, $attribute, $value)
{
$browser->script("Nova.\$emit('{$this->formUniqueId}-{$attribute}-value', '{$value}')");
}
/**
* Assert that the browser page contains the component.
*
* @param \Laravel\Dusk\Browser $browser
* @return void
*
* @throws \Facebook\WebDriver\Exception\TimeOutException
*/
public function assert(Browser $browser)
{
tap($this->selector(), function ($selector) use ($browser) {
$browser->pause(100)
->waitFor($selector)
->assertVisible($selector)
->scrollIntoView($selector);
$this->formUniqueId = $browser->attribute($selector, 'data-form-unique-id');
});
}
}

View File

@@ -0,0 +1,57 @@
<?php
namespace Laravel\Nova\Testing\Browser\Components;
use Laravel\Dusk\Browser;
class HeaderComponent extends Component
{
/**
* Get the root selector for the component.
*
* @return string
*/
public function selector()
{
return 'div#app header';
}
/**
* Assert that the browser page contains the component.
*
* @return void
*
* @throws \Facebook\WebDriver\Exception\TimeOutException
*/
public function assert(Browser $browser)
{
tap($this->selector(), function ($selector) use ($browser) {
$browser->scrollIntoView($selector);
});
}
/**
* Open notification panel.
*
* @param callable|null $notificationCallback
* @return void
*/
public function showNotificationPanel(Browser $browser, $notificationCallback = null)
{
$browser
->click('@notifications-dropdown')
->elsewhereWhenAvailable('@notifications-content', $notificationCallback ?? function ($browser) {
//
});
}
/**
* Get the element shortcuts for the component.
*
* @return array
*/
public function elements()
{
return [];
}
}

View File

@@ -0,0 +1,744 @@
<?php
namespace Laravel\Nova\Testing\Browser\Components;
use Illuminate\Support\Str;
use Laravel\Dusk\Browser;
class IndexComponent extends Component
{
public $resourceName;
public $viaRelationship;
/**
* Create a new component instance.
*
* @param string $resourceName
* @param string|null $viaRelationship
* @return void
*/
public function __construct($resourceName, $viaRelationship = null)
{
$this->resourceName = $resourceName;
if (! is_null($viaRelationship) && $resourceName !== $viaRelationship) {
$this->viaRelationship = $viaRelationship;
}
}
/**
* Get the root selector for the component.
*
* @return string
*/
public function selector()
{
$selector = '[dusk="'.$this->resourceName.'-index-component"]';
return sprintf(
! is_null($this->viaRelationship) ? '%s[data-relationship="%s"]' : '%s', $selector, $this->viaRelationship
);
}
/**
* Wait for table to be ready.
*
* @param int|null $seconds
* @return void
*
* @throws \Facebook\WebDriver\Exception\TimeOutException
*/
public function waitForTable(Browser $browser, $seconds = null)
{
$browser->waitUntilMissing('@loading-view')
->whenAvailable('@resource-table', function ($browser) use ($seconds) {
$browser->waitFor('tbody', $seconds);
}, $seconds);
}
/**
* Wait for empty dialog to be ready.
*
* @param int|null $seconds
* @return void
*
* @throws \Facebook\WebDriver\Exception\TimeOutException
*/
public function waitForEmptyDialog(Browser $browser, $seconds = null)
{
$browser->waitUntilMissing('@loading-view')
->waitFor('div[dusk="'.$this->resourceName.'-empty-dialog"]', $seconds);
}
/**
* Search for the given string.
*
* @param string $search
* @return void
*/
public function searchFor(Browser $browser, $search)
{
$browser->type('@search-input', $search)->pause(1000);
}
/**
* Clear the search field.
*
* @return void
*/
public function clearSearch(Browser $browser)
{
$browser->clear('@search-input')->type('@search-input', ' ')->pause(1000);
}
/**
* Click the sortable icon for the given attribute.
*
* @param string $attribute
* @return void
*/
public function sortBy(Browser $browser, $attribute)
{
$browser->click("@sort-{$attribute}")->waitForTable();
}
/**
* Paginate to the next page of resources.
*
* @return void
*/
public function nextPage(Browser $browser)
{
$browser->click('@next')->waitForTable();
}
/**
* Paginate to the previous page of resources.
*
* @return void
*/
public function previousPage(Browser $browser)
{
$browser->click('@previous')->waitForTable();
}
/**
* Select all the the resources on current page.
*
* @return void
*/
public function selectAllOnCurrentPage(Browser $browser)
{
$browser->within(new SelectAllDropdownComponent(), function (Browser $browser) {
$browser->selectAllOnCurrentPage();
});
}
/**
* Un-select all the the resources on current page.
*
* @return void
*/
public function unselectAllOnCurrentPage(Browser $browser)
{
$browser->within(new SelectAllDropdownComponent(), function ($browser) {
$browser->unselectAllOnCurrentPage();
});
}
/**
* Select all the matching resources.
*
* @return void
*/
public function selectAllMatching(Browser $browser)
{
$browser->within(new SelectAllDropdownComponent(), function ($browser) {
$browser->selectAllMatching();
});
}
/**
* Un-select all the matching resources.
*
* @return void
*/
public function unselectAllMatching(Browser $browser)
{
$browser->within(new SelectAllDropdownComponent(), function ($browser) {
$browser->unselectAllMatching();
});
}
/**
* Assert on the matching total matching count text.
*
* @param int $count
* @return void
*/
public function assertSelectAllMatchingCount(Browser $browser, $count)
{
$browser->within(new SelectAllDropdownComponent(), function ($browser) use ($count) {
$browser->assertSelectAllMatchingCount($count);
});
}
/**
* Set the given filter and filter value for the index.
*
* @param callable|null $fieldCallback
* @param callable|bool|null $postCallback
* @return void
*/
public function runFilter(Browser $browser, $fieldCallback = null, $postCallback = null)
{
$browser->openFilterSelector();
if (is_callable($fieldCallback)) {
$browser->elsewhereWhenAvailable('@filter-menu', function (Browser $browser) use ($fieldCallback) {
$fieldCallback($browser);
});
}
if ($postCallback !== false) {
call_user_func($postCallback ?? function ($browser) {
$browser->closeCurrentDropdown();
}, $browser);
}
}
/**
* Reset current filter value for the index.
*
* @return void
*/
public function resetFilter(Browser $browser)
{
$this->runFilter($browser, function ($browser) {
$browser->press(Str::upper(__('Reset Filters')));
});
}
/**
* Assert current filter count for the index.
*
* @return void
*/
public function assertFilterCount(Browser $browser, int $count)
{
$browser->within('@filter-selector-button', function ($browser) use ($count) {
if ($count <= 0) {
$browser->assertDontSee($count);
} else {
$browser->assertSee($count);
}
});
}
/**
* Set the per page value for the index.
*
* @return void
*/
public function setPerPage(Browser $browser, $value)
{
$this->runFilter($browser, function ($browser) use ($value) {
$browser->whenAvailable('select[dusk="per-page-select"]', function ($browser) use ($value) {
$browser->select('', $value);
});
});
}
/**
* Set the given filter and filter value for the index.
*
* @param string $name
* @param string $value
* @return void
*/
public function selectFilter(Browser $browser, $name, $value)
{
$this->runFilter($browser, function ($browser) use ($name, $value) {
$browser->whenAvailable('select[dusk="'.$name.'-select-filter"]', function ($browser) use ($value) {
$browser->select('', $value)->pause(1000);
});
});
}
/**
* Indicate that trashed records should not be displayed.
*
* @return void
*/
public function withoutTrashed(Browser $browser)
{
$this->runFilter($browser, function ($browser) {
$browser->whenAvailable('[dusk="filter-soft-deletes"]', function ($browser) {
$browser->select('select[dusk="trashed-select"]', '')->pause(1000);
});
});
}
/**
* Indicate that only trashed records should be displayed.
*
* @return void
*/
public function onlyTrashed(Browser $browser)
{
$this->runFilter($browser, function ($browser) {
$browser->whenAvailable('[dusk="filter-soft-deletes"]', function ($browser) {
$browser->select('select[dusk="trashed-select"]', 'only')->pause(1000);
});
});
}
/**
* Indicate that trashed records should be displayed.
*
* @return void
*/
public function withTrashed(Browser $browser)
{
$this->runFilter($browser, function ($browser) {
$browser->whenAvailable('[dusk="filter-soft-deletes"]', function ($browser) {
$browser->select('select[dusk="trashed-select"]', 'with');
});
});
}
/**
* Open the action selector.
*
* @return void
*
* @throws \Facebook\WebDriver\Exception\TimeOutException
*/
public function openActionSelector(Browser $browser)
{
$browser->whenAvailable('@action-select', function ($browser) {
$browser->click('')->pause(100);
});
}
/**
* Open the standalone action selector.
*
* @return void
*
* @throws \Facebook\WebDriver\Exception\TimeOutException
*/
public function openStandaloneActionSelector(Browser $browser)
{
$browser->whenAvailable('@index-standalone-action-dropdown', function ($browser) {
$browser->click('')->pause(100);
});
}
/**
* Open the filter selector.
*
* @return void
*
* @throws \Facebook\WebDriver\Exception\TimeOutException
*/
public function openFilterSelector(Browser $browser)
{
$browser->whenAvailable('@filter-selector', function ($browser) {
$browser->click('')->pause(100);
});
}
/**
* Open the action selector.
*
* @param int|string $id
* @return void
*
* @throws \Facebook\WebDriver\Exception\TimeOutException
*/
public function openControlSelectorById(Browser $browser, $id)
{
$browser
->whenAvailable("@{$id}-control-selector", function ($browser) {
$browser->click('')->pause(300);
});
}
/**
* assert the action selector is present by ID.
*
* @param int|string $id
* @return void
*/
public function assertPresentControlSelectorById(Browser $browser, $id)
{
$browser->assertPresent("@{$id}-control-selector");
}
/**
* assert the action selector is missing by ID.
*
* @param int|string $id
* @return void
*/
public function assertMissingControlSelectorById(Browser $browser, $id)
{
$browser->assertMissing("@{$id}-control-selector");
}
/**
* Select the action with the given URI key.
*
* @param string $uriKey
* @param callable $fieldCallback
* @return void
*
* @throws \Facebook\WebDriver\Exception\TimeOutException
*/
public function selectAction(Browser $browser, $uriKey, $fieldCallback)
{
$browser->whenAvailable('select[dusk="action-select"]', function ($browser) use ($uriKey) {
$browser->select('', $uriKey)
->pause(100)
->assertSelected('', '');
});
$browser->elsewhereWhenAvailable(new Modals\ConfirmActionModalComponent(), function ($browser) use ($fieldCallback) {
$fieldCallback($browser);
});
}
/**
* Select the standalone action with the given URI key.
*
* @param string $uriKey
* @param callable $fieldCallback
* @return void
*
* @throws \Facebook\WebDriver\Exception\TimeOutException
*/
public function selectStandaloneAction(Browser $browser, $uriKey, $fieldCallback)
{
$browser->whenAvailable('@index-standalone-action-dropdown', function ($browser) {
$browser->click('');
})->elseWhereWhenAvailable('div[data-menu-open="true"]', function ($browser) use ($uriKey) {
$browser->click("button[data-action-id='{$uriKey}']");
});
$browser->elsewhereWhenAvailable(new Modals\ConfirmActionModalComponent(), function ($browser) use ($fieldCallback) {
$fieldCallback($browser);
});
}
/**
* Run the action with the given URI key.
*
* @param string $uriKey
* @param callable|null $fieldCallback
* @return void
*
* @throws \Facebook\WebDriver\Exception\TimeOutException
*/
public function runAction(Browser $browser, $uriKey, $fieldCallback = null)
{
$this->selectAction($browser, $uriKey, function ($browser) use ($fieldCallback) {
if ($fieldCallback) {
$fieldCallback($browser);
}
$browser->waitForText('Run Action')->click('@confirm-action-button')->pause(250);
});
}
/**
* Run the standalone action with the given URI key.
*
* @param string $uriKey
* @param callable|null $fieldCallback
* @return void
*
* @throws \Facebook\WebDriver\Exception\TimeOutException
*/
public function runStandaloneAction(Browser $browser, $uriKey, $fieldCallback = null)
{
$this->selectStandaloneAction($browser, $uriKey, function ($browser) use ($fieldCallback) {
$browser->pause(2000);
if ($fieldCallback) {
$fieldCallback($browser);
}
$browser->waitForText('Run Action')->click('@confirm-action-button')->pause(250);
});
}
/**
* Select the action with the given URI key.
*
* @param int|string $id
* @param string $uriKey
* @param callable $fieldCallback
* @return void
*/
public function selectInlineAction(Browser $browser, $id, $uriKey, $fieldCallback)
{
$browser->openControlSelectorById($id)
->elseWhereWhenAvailable('div[data-menu-open="true"]', function ($browser) use ($uriKey) {
$browser->click("button[data-action-id='{$uriKey}']");
})->pause(500);
$browser->elsewhereWhenAvailable(new Modals\ConfirmActionModalComponent(), function ($browser) use ($fieldCallback) {
$fieldCallback($browser);
});
}
/**
* Run the action with the given URI key.
*
* @param int|string $id
* @param string $uriKey
* @param callable|null $fieldCallback
* @return void
*/
public function runInlineAction(Browser $browser, $id, $uriKey, $fieldCallback = null)
{
$this->selectInlineAction($browser, $id, $uriKey, function ($browser) use ($fieldCallback) {
if ($fieldCallback) {
$fieldCallback($browser);
}
$browser->click('@confirm-action-button')->pause(250);
});
}
/**
* Check the user at the given resource table row index.
*
* @param int|string $id
* @param int|string|null $pivotId
* @return void
*/
public function clickCheckboxForId(Browser $browser, $id, $pivotId = null)
{
if (! is_null($pivotId)) {
$browser->click('[data-pivot-id="'.$pivotId.'"][dusk="'.$id.'-row"] [role="checkbox"]');
} else {
$browser->click('[dusk="'.$id.'-checkbox"]');
}
$browser->pause(175);
}
/**
* Replicate the given resource table row index.
*
* @param int|string $id
* @return void
*/
public function replicateResourceById(Browser $browser, $id)
{
$browser->openControlSelectorById($id)
->elsewhereWhenAvailable("@{$id}-replicate-button", function ($browser) {
$browser->click('');
})->pause(500);
}
/**
* Preview the given resource table row index.
*
* @param int|string $id
* @return void
*/
public function previewResourceById(Browser $browser, $id)
{
$browser->openControlSelectorById($id)
->elsewhereWhenAvailable("@{$id}-preview-button", function ($browser) {
$browser->click('');
})->pause(500);
}
/**
* Delete the user at the given resource table row index.
*
* @param int|string $id
* @return void
*/
public function deleteResourceById(Browser $browser, $id)
{
$browser->click("@{$id}-delete-button")
->elsewhereWhenAvailable(new Modals\DeleteResourceModalComponent(), function ($browser) {
$browser->confirm();
})->pause(500);
}
/**
* Restore the user at the given resource table row index.
*
* @param int|string $id
* @return void
*/
public function restoreResourceById(Browser $browser, $id)
{
$browser->click("@{$id}-restore-button")
->elsewhereWhenAvailable(new Modals\RestoreResourceModalComponent(), function ($browser) {
$browser->confirm();
})->pause(500);
}
/**
* View the user at the given resource table row index.
*
* @param int|string $id
* @return void
*/
public function viewResourceById(Browser $browser, $id)
{
$browser->click("@{$id}-view-button")->pause(500);
}
/**
* Edit the user at the given resource table row index.
*
* @param int|string $id
* @return void
*/
public function editResourceById(Browser $browser, $id)
{
$browser->click("@{$id}-edit-button")->pause(500);
}
/**
* Delete the resources selected via checkboxes.
*
* @return void
*/
public function deleteSelected(Browser $browser)
{
$browser->click('@delete-menu')
->pause(300)
->elsewhere('', function ($browser) {
$browser->click('[dusk="delete-selected-button"]')
->elsewhereWhenAvailable(new Modals\DeleteResourceModalComponent(), function ($browser) {
$browser->confirm();
});
})->pause(1000);
}
/**
* Restore the resources selected via checkboxes.
*
* @return void
*/
public function restoreSelected(Browser $browser)
{
$browser->click('@delete-menu')
->pause(300)
->elsewhere('', function ($browser) {
$browser->click('[dusk="restore-selected-button"]')
->elsewhereWhenAvailable(new Modals\RestoreResourceModalComponent(), function ($browser) {
$browser->confirm();
});
})->pause(1000);
}
/**
* Restore the resources selected via checkboxes.
*
* @return void
*/
public function forceDeleteSelected(Browser $browser)
{
$browser->click('@delete-menu')
->pause(300)
->elsewhere('', function ($browser) {
$browser->click('[dusk="force-delete-selected-button"]')
->elsewhereWhenAvailable(new Modals\DeleteResourceModalComponent(), function ($browser) {
$browser->confirm();
});
})->pause(1000);
}
/**
* Assert that the browser page contains the component.
*
* @return void
*
* @throws \Facebook\WebDriver\Exception\TimeOutException
*/
public function assert(Browser $browser)
{
$browser->pause(500);
tap($this->selector(), function ($selector) use ($browser) {
$browser->waitFor($selector)
->assertVisible($selector)
->scrollIntoView($selector);
});
}
/**
* Assert that the given resource is visible.
*
* @param int|string $id
* @param int|string|null $pivotId
* @return void
*/
public function assertSeeResource(Browser $browser, $id, $pivotId = null)
{
if (! is_null($pivotId)) {
$browser->assertVisible('[dusk="'.$id.'-row"][data-pivot-id="'.$pivotId.'"]');
} else {
$browser->assertVisible("@{$id}-row");
}
}
/**
* Assert that the given resource is not visible.
*
* @param int|string $id
* @param int|string|null $pivotId
* @return void
*/
public function assertDontSeeResource(Browser $browser, $id, $pivotId = null)
{
if (! is_null($pivotId)) {
$browser->assertMissing('[dusk="'.$id.'-row"][data-pivot-id="'.$pivotId.'"]');
} else {
$browser->assertMissing("@{$id}-row");
}
}
/**
* Assert that the checkbox is checked.
*
* @return void
*/
public function assertCheckboxChecked(Browser $browser, $selector)
{
$browser->assertAttribute($selector, 'data-state', 'checked');
}
/**
* Assert that the checkbox is not checked.
*
* @return void
*/
public function assertCheckboxNotChecked(Browser $browser, $selector)
{
$browser->assertAttribute($selector, 'data-state', 'unchecked');
}
/**
* Get the element shortcuts for the component.
*
* @return array
*/
public function elements()
{
return [
'@nova-opened-modal' => '.modal[data-modal-open=true]',
];
}
}

View File

@@ -0,0 +1,31 @@
<?php
namespace Laravel\Nova\Testing\Browser\Components;
class LensComponent extends IndexComponent
{
public $lens;
/**
* Create a new component instance.
*
* @param string $resourceName
* @param string $lens
* @return void
*/
public function __construct($resourceName, $lens)
{
$this->lens = $lens;
$this->resourceName = $resourceName;
}
/**
* Get the root selector for the component.
*
* @return string
*/
public function selector()
{
return '@'.$this->lens.'-lens-component';
}
}

View File

@@ -0,0 +1,30 @@
<?php
namespace Laravel\Nova\Testing\Browser\Components\Modals;
use Laravel\Dusk\Browser;
class ConfirmActionModalComponent extends ModalComponent
{
/**
* Modal confirmation button.
*
* @param \Laravel\Dusk\Browser $browser
* @return void
*/
public function confirm(Browser $browser)
{
$browser->click('@confirm-action-button');
}
/**
* Modal cancelation button.
*
* @param \Laravel\Dusk\Browser $browser
* @return void
*/
public function cancel(Browser $browser)
{
$browser->click('@cancel-action-button');
}
}

View File

@@ -0,0 +1,30 @@
<?php
namespace Laravel\Nova\Testing\Browser\Components\Modals;
use Laravel\Dusk\Browser;
class ConfirmUploadRemovalModalComponent extends ModalComponent
{
/**
* Modal confirmation button.
*
* @param \Laravel\Dusk\Browser $browser
* @return void
*/
public function confirm(Browser $browser)
{
$browser->click('@confirm-upload-delete-button');
}
/**
* Modal cancelation button.
*
* @param \Laravel\Dusk\Browser $browser
* @return void
*/
public function cancel(Browser $browser)
{
$browser->click('@cancel-upload-delete-button');
}
}

View File

@@ -0,0 +1,30 @@
<?php
namespace Laravel\Nova\Testing\Browser\Components\Modals;
use Laravel\Dusk\Browser;
class CreateRelationModalComponent extends ModalComponent
{
/**
* Modal confirmation button.
*
* @param \Laravel\Dusk\Browser $browser
* @return void
*/
public function confirm(Browser $browser)
{
$browser->click('@create-button');
}
/**
* Modal cancelation button.
*
* @param \Laravel\Dusk\Browser $browser
* @return void
*/
public function cancel(Browser $browser)
{
$browser->click('@cancel-create-button');
}
}

View File

@@ -0,0 +1,30 @@
<?php
namespace Laravel\Nova\Testing\Browser\Components\Modals;
use Laravel\Dusk\Browser;
class DeleteResourceModalComponent extends ModalComponent
{
/**
* Modal confirmation button.
*
* @param \Laravel\Dusk\Browser $browser
* @return void
*/
public function confirm(Browser $browser)
{
$browser->click('@confirm-delete-button');
}
/**
* Modal cancelation button.
*
* @param \Laravel\Dusk\Browser $browser
* @return void
*/
public function cancel(Browser $browser)
{
$browser->click('@cancel-delete-button');
}
}

View File

@@ -0,0 +1,35 @@
<?php
namespace Laravel\Nova\Testing\Browser\Components\Modals;
use Laravel\Dusk\Browser;
use Laravel\Nova\Testing\Browser\Components\Component;
abstract class ModalComponent extends Component
{
/**
* Get the root selector for the component.
*
* @return string
*/
public function selector()
{
return '.modal[data-modal-open=true]';
}
/**
* Modal confirmation button.
*
* @param \Laravel\Dusk\Browser $browser
* @return void
*/
abstract public function confirm(Browser $browser);
/**
* Modal cancelation button.
*
* @param \Laravel\Dusk\Browser $browser
* @return void
*/
abstract public function cancel(Browser $browser);
}

View File

@@ -0,0 +1,52 @@
<?php
namespace Laravel\Nova\Testing\Browser\Components\Modals;
use Laravel\Dusk\Browser;
class PreviewResourceModalComponent extends ModalComponent
{
/**
* Modal confirmation button.
*
* @param \Laravel\Dusk\Browser $browser
* @return void
*/
public function confirm(Browser $browser)
{
$browser->click('@confirm-preview-button');
}
/**
* Modal cancelation button.
*
* @param \Laravel\Dusk\Browser $browser
* @return void
*/
public function cancel(Browser $browser)
{
$browser->click('@confirm-preview-button');
}
/**
* Modal view detail button.
*
* @param \Laravel\Dusk\Browser $browser
* @return void
*/
public function view(Browser $browser)
{
$browser->click('@detail-preview-button');
}
/**
* Assert modal view detail button is visible.
*
* @param \Laravel\Dusk\Browser $browser
* @return void
*/
public function assertViewButtonVisible(Browser $browser)
{
$browser->assertVisible('@detail-preview-button');
}
}

View File

@@ -0,0 +1,28 @@
<?php
namespace Laravel\Nova\Testing\Browser\Components\Modals;
use Laravel\Dusk\Browser;
class RestoreResourceModalComponent extends ModalComponent
{
/**
* Modal confirmation button.
*
* @return void
*/
public function confirm(Browser $browser)
{
$browser->click('@confirm-restore-button');
}
/**
* Modal cancelation button.
*
* @return void
*/
public function cancel(Browser $browser)
{
$browser->click('@cancel-restore-button');
}
}

View File

@@ -0,0 +1,283 @@
<?php
namespace Laravel\Nova\Testing\Browser\Components;
use Facebook\WebDriver\WebDriverKeys;
use Illuminate\Support\Arr;
use Laravel\Dusk\Browser;
use Laravel\Dusk\ElementResolver;
class SearchInputComponent extends Component
{
public $attribute;
public $mode;
/**
* Create a new component instance.
*
* @param string $attribute
* @param string $mode
* @return void
*/
public function __construct(string $attribute, string $mode = 'input')
{
$this->attribute = $attribute;
$this->mode = $mode;
}
/**
* Show the component dropdown.
*
* @param \Laravel\Dusk\Browser $browser
* @param string $search
* @return void
*/
public function showSearchDropdown(Browser $browser)
{
$resolver = new ElementResolver($browser->driver, 'body');
$input = $resolver->find("[dusk='{$this->attribute}-search-{$this->mode}-dropdown']");
if (is_null($input) || ! $input->isDisplayed()) {
$browser->click('');
}
}
/**
* Search for the given value for a searchable field attribute.
*
* @param \Laravel\Dusk\Browser $browser
* @param string $search
* @param int $pause
* @return void
*/
public function searchInput(Browser $browser, $search, int $pause = 500)
{
$this->showSearchDropdown($browser);
$browser->elsewhereWhenAvailable("{$this->selector()}-dropdown", function ($browser) use ($search) {
$browser->type('input[type="search"]', $search);
});
$browser->pause($pause);
}
/**
* Reset the searchable field.
*
* @param \Laravel\Dusk\Browser $browser
* @return void
*/
public function resetSearchResult(Browser $browser)
{
$this->cancelSelectingSearchResult($browser);
$selector = "{$this->selector()}-clear-button";
$element = $browser->element($selector);
if (! is_null($element) && $element->isDisplayed()) {
$browser->click($selector)->pause(1500);
}
}
/**
* Search and select the searchable field by result index.
*
* @param \Laravel\Dusk\Browser $browser
* @param string $search
* @param int $resultIndex
* @return void
*/
public function searchAndSelectResult(Browser $browser, $search, $resultIndex)
{
$this->searchInput($browser, $search, 1500);
$this->selectSearchResult($browser, $resultIndex);
}
/**
* Select the searchable field by result index.
*
* @param \Laravel\Dusk\Browser $browser
* @param int $resultIndex
* @return void
*/
public function selectSearchResult(Browser $browser, $resultIndex)
{
$browser->elseWhereWhenAvailable("{$this->selector()}-dropdown", function ($browser) use ($resultIndex) {
$browser->whenAvailable("{$this->selector()}-result-{$resultIndex}", function ($browser) {
$browser->click('')->pause(300);
});
});
}
/**
* Select the currently highlighted searchable field.
*
* @param \Laravel\Dusk\Browser $browser
* @return void
*/
public function cancelSelectingSearchResult(Browser $browser)
{
$browser->driver->getKeyboard()->sendKeys(WebDriverKeys::ESCAPE);
$browser->pause(150);
}
/**
* Select the currently highlighted searchable field.
*
* @param \Laravel\Dusk\Browser $browser
* @return void
*/
public function selectFirstSearchResult(Browser $browser)
{
$this->selectSearchResult($browser, 0);
}
/**
* Search and select the currently highlighted searchable relation.
*
* @param \Laravel\Dusk\Browser $browser
* @param string $search
* @return void
*/
public function searchFirstRelation(Browser $browser, $search)
{
$this->searchAndSelectFirstResult($browser, $search);
}
/**
* Search and select the currently highlighted searchable field.
*
* @param \Laravel\Dusk\Browser $browser
* @param string $search
* @return void
*/
public function searchAndSelectFirstResult(Browser $browser, $search)
{
$this->searchAndSelectResult($browser, $search, 0);
}
/**
* Assert on searchable results.
*
* @param \Laravel\Dusk\Browser $browser
* @param callable(\Laravel\Nova\Browser, string):void $fieldCallback
* @return void
*/
public function assertSearchResult(Browser $browser, callable $fieldCallback)
{
$this->showSearchDropdown($browser);
$browser->elsewhereWhenAvailable("{$this->selector()}-dropdown", function ($browser) use ($fieldCallback) {
$fieldCallback($browser, $this->selector());
$this->cancelSelectingSearchResult($browser);
});
}
/**
* Assert on searchable results is locked to single result.
*
* @param \Laravel\Dusk\Browser $browser
* @param string $search
* @return void
*/
public function assertSelectedSearchResult(Browser $browser, $search)
{
$browser->assertSeeIn("{$this->selector()}-selected", $search);
}
/**
* Assert on searchable results is locked to single result.
*
* @param \Laravel\Dusk\Browser $browser
* @param string $search
* @return void
*/
public function assertSelectedFirstSearchResult(Browser $browser, $search)
{
$this->assertSelectedSearchResult($browser, $search);
$this->assertSearchResult($browser, function ($browser, $attribute) use ($search) {
$browser->assertSeeIn("{$attribute}-result-0", $search)
->assertNotPresent("{$attribute}-result-1")
->assertNotPresent("{$attribute}-result-2")
->assertNotPresent("{$attribute}-result-3")
->assertNotPresent("{$attribute}-result-4");
});
}
/**
* Assert on searchable results is empty.
*
* @param \Laravel\Dusk\Browser $browser
* @return void
*/
public function assertEmptySearchResult(Browser $browser)
{
$this->assertSearchResult($browser, function ($browser, $attribute) {
$browser->assertNotPresent("{$attribute}-result-0")
->assertNotPresent("{$attribute}-result-1")
->assertNotPresent("{$attribute}-result-2")
->assertNotPresent("{$attribute}-result-3")
->assertNotPresent("{$attribute}-result-4");
});
}
/**
* Assert on searchable results has the search value.
*
* @param \Laravel\Dusk\Browser $browser
* @param string|array $search
* @return void
*/
public function assertSearchResultContains(Browser $browser, $search)
{
$this->assertSearchResult($browser, function ($browser, $attribute) use ($search) {
foreach (Arr::wrap($search) as $keyword) {
$browser->assertSeeIn("{$attribute}-results", $keyword);
}
});
}
/**
* Assert on searchable results doesn't has the search value.
*
* @param \Laravel\Dusk\Browser $browser
* @param string|array $search
* @return void
*/
public function assertSearchResultDoesNotContains(Browser $browser, $search)
{
$this->assertSearchResult($browser, function ($browser, $attribute) use ($search) {
foreach (Arr::wrap($search) as $keyword) {
$browser->assertDontSeeIn("{$attribute}-results", $keyword);
}
});
}
/**
* Assert that the current page contains this component.
*
* @param \Laravel\Dusk\Browser $browser
* @return void
*/
public function assert(Browser $browser)
{
$browser->waitFor($this->selector());
}
/**
* Get the root selector associated with this component.
*
* @return string
*/
public function selector()
{
return "@{$this->attribute}-search-{$this->mode}";
}
}

View File

@@ -0,0 +1,22 @@
<?php
namespace Laravel\Nova\Testing\Browser\Components;
use Laravel\Dusk\Browser;
class SearchSearchInputComponent extends SearchInputComponent
{
/**
* Search for the given value for a searchable field attribute.
*
* @param \Laravel\Dusk\Browser $browser
* @param string $search
* @return void
*/
public function searchInput(Browser $browser, $search, int $pause = 500)
{
$this->showSearchDropdown($browser);
$browser->type('input[type="search"]', $search);
}
}

View File

@@ -0,0 +1,205 @@
<?php
namespace Laravel\Nova\Testing\Browser\Components;
use Laravel\Dusk\Browser;
class SelectAllDropdownComponent extends Component
{
/**
* Get the root selector for the component.
*
* @return string
*/
public function selector()
{
return '@select-all-dropdown';
}
protected function assertCheckboxStatus(Browser $browser, $status)
{
$browser->assertAttribute('@select-all-indicator', 'data-state', $status);
}
/**
* Assert that the checkbox is checked.
*
* @return void
*/
public function assertCheckboxIsChecked(Browser $browser)
{
$this->assertCheckboxStatus($browser, 'checked');
}
/**
* Assert that the checkbox is not checked.
*
* @return void
*/
public function assertCheckboxIsNotChecked(Browser $browser)
{
$this->assertCheckboxStatus($browser, 'unchecked');
}
/**
* Assert that the checkbox is indeterminate.
*
* @return void
*/
public function assertCheckboxIsIndeterminate(Browser $browser)
{
$this->assertCheckboxStatus($browser, 'indeterminate');
}
/**
* Assert select all the the resources on current page is checked.
*
* @return void
*/
public function assertSelectAllOnCurrentPageChecked(Browser $browser)
{
$this->assertCheckboxIsIndeterminate($browser);
}
/**
* Assert select all the the resources on current page isn't checked.
*
* @return void
*/
public function assertSelectAllOnCurrentPageNotChecked(Browser $browser)
{
$this->assertCheckboxIsNotChecked($browser);
}
/**
* Assert select all the matching resources is checked.
*
* @return void
*/
public function assertSelectAllMatchingChecked(Browser $browser)
{
$browser
->waitFor('@select-all-dropdown-trigger')
->click('@select-all-dropdown-trigger')
->elsewhereWhenAvailable('@dropdown-menu', function (Browser $browser) {
$browser->assertAttribute('@select-all-matching-button', 'data-state', 'checked');
})
->closeCurrentDropdown();
}
/**
* Assert select all the matching resources isn't checked.
*
* @return void
*/
public function assertSelectAllMatchingNotChecked(Browser $browser)
{
$browser
->waitFor('@select-all-dropdown-trigger')
->click('@select-all-dropdown-trigger')
->elsewhereWhenAvailable('@dropdown-menu', function (Browser $browser) {
$browser->assertAttribute('@select-all-matching-button', 'data-state', 'unchecked');
})
->closeCurrentDropdown();
}
/**
* Assert on the total selected count text.
*
* @param int $count
* @return void
*/
public function assertSelectedCount(Browser $browser, $count)
{
$browser->assertSeeIn('span.font-bold', "{$count} selected");
}
/**
* Assert on the matching total matching count text.
*
* @param int $count
* @return void
*/
public function assertSelectAllMatchingCount(Browser $browser, $count)
{
$browser
->waitFor('@select-all-dropdown-trigger')
->click('@select-all-dropdown-trigger')
->elsewhereWhenAvailable('@dropdown-menu', function (Browser $browser) use ($count) {
$browser->assertSeeIn('@select-all-matching-count', $count);
})
->closeCurrentDropdown();
}
/**
* Select all the the resources on current page.
*
* @return void
*/
public function selectAllOnCurrentPage(Browser $browser)
{
$browser
->waitFor('@select-all-dropdown-trigger')
->click('@select-all-dropdown-trigger')
->elsewhereWhenAvailable('@dropdown-menu', function ($browser) {
$browser->click('@select-all-button');
})
->closeCurrentDropdown();
}
/**
* Un-select all the the resources on current page.
*
* @return void
*/
public function unselectAllOnCurrentPage(Browser $browser)
{
$browser->waitFor('@deselect-all-button')
->click('@deselect-all-button')->pause(250);
}
/**
* Select all the matching resources.
*
* @return void
*/
public function selectAllMatching(Browser $browser)
{
$browser
->waitFor('@select-all-dropdown-trigger')
->click('@select-all-dropdown-trigger')
->elsewhereWhenAvailable('@dropdown-menu', function ($browser) {
$browser->click('@select-all-matching-button');
})
->closeCurrentDropdown();
}
/**
* Un-select all the matching resources.
*
* @return void
*/
public function unselectAllMatching(Browser $browser)
{
$browser
->waitFor('@select-all-dropdown-trigger', 2)
->click('@select-all-dropdown-trigger')
->elsewhereWhenAvailable('@dropdown-menu', function ($browser) {
$browser->click('@select-all-matching-button')->pause(250);
}, 2);
}
/**
* Assert that the browser page contains the component.
*
* @return void
*
* @throws \Facebook\WebDriver\Exception\TimeOutException
*/
public function assert(Browser $browser)
{
tap($this->selector(), function ($selector) use ($browser) {
$browser->scrollIntoView($selector);
});
}
}

View File

@@ -0,0 +1,63 @@
<?php
namespace Laravel\Nova\Testing\Browser\Components;
use Laravel\Dusk\Browser;
class SidebarComponent extends Component
{
/**
* The screen size 'desktop', 'responsive'.
*
* @var string
*/
public $screen = 'desktop';
/**
* Create a new component instance.
*
* @param string|null $screen
* @return void
*/
public function __construct($screen = null)
{
$this->screen = $screen ?? $this->screen;
}
/**
* Get the root selector for the component.
*
* @return string
*/
public function selector()
{
return 'div[dusk="sidebar-menu"][role="navigation"][data-screen="'.$this->screen.'"]';
}
/**
* Assert that the browser page contains the component.
*
* @param \Laravel\Dusk\Browser $browser
* @return void
*
* @throws \Facebook\WebDriver\Exception\TimeOutException
*/
public function assert(Browser $browser)
{
tap($this->selector(), function ($selector) use ($browser) {
$browser->scrollIntoView($selector);
});
}
/**
* Get the element shortcuts for the component.
*
* @return array
*/
public function elements()
{
return [
'@current-active-link' => 'a[data-active-link=true]',
];
}
}

View File

@@ -0,0 +1,145 @@
<?php
namespace Laravel\Nova\Testing\Browser\Concerns;
use Carbon\CarbonInterface;
use Facebook\WebDriver\Exception\WebDriverException;
use Illuminate\Support\Env;
use Laravel\Dusk\Browser;
trait InteractsWithElements
{
/**
* Dismiss toasted messages.
*
* @return void
*/
public function dismissToasted(Browser $browser)
{
$browser->script('Nova.$toasted.clear()');
}
/**
* Close current dropdown.
*
* @return void
*/
public function closeCurrentDropdown(Browser $browser, bool $throwIfMissing = false)
{
try {
$browser->elseWhereWhenAvailable('@dropdown-teleported', function (Browser $browser) {
$element = $browser->element('@dropdown-overlay');
if (! is_null($element) && $element->isDisplayed()) {
$browser->click('@dropdown-overlay')->waitUntilMissing('@dropdown-overlay');
}
});
} catch (WebDriverException $e) {
if ($throwIfMissing === true) {
throw $e;
}
}
}
/**
* Type on "date" input.
*
* @param \Carbon\CarbonInterface|empty-string|null $carbon
* @return void
*/
public function typeOnDate(Browser $browser, string $selector, $carbon)
{
if ($carbon instanceof CarbonInterface) {
$date = $carbon->format(Env::get('DUSK_DATE_FORMAT', 'mdY'));
$this->typeWithTabs($browser, $selector, $date);
} else {
$browser->type($selector, '');
}
$browser->pause(1000);
}
/**
* Type in a "datetime" filter input.
*
* @param \Carbon\CarbonInterface|empty-string|null $carbon
* @return void
*/
public function typeInDateTimeField(Browser $browser, string $selector, $carbon)
{
if ($carbon instanceof CarbonInterface) {
$this->typeWithTabs($browser, $selector, $carbon->format(Env::get('DUSK_DATETIME_FORMAT', 'mdY-hia')));
$browser->keys($selector, ['{tab}']);
} else {
$browser->type($selector, '');
}
$browser->pause(1000);
}
/**
* Type on "datetime-local" input.
*
* @param \Carbon\CarbonInterface|empty-string|null $carbon
* @return void
*/
public function typeOnDateTimeLocal(Browser $browser, string $selector, $carbon)
{
if ($carbon instanceof CarbonInterface) {
$date = $carbon->format(Env::get('DUSK_DATE_FORMAT', 'mdY'));
$time = $carbon->format(Env::get('DUSK_TIME_FORMAT', 'hisa'));
$this->typeWithTabs($browser, $selector, $date);
$browser->keys($selector, ['{tab}']);
$this->typeWithTabs($browser, $selector, $time);
} else {
$browser->type($selector, '');
}
}
/**
* Type input separated using "tab".
*
* @return void
*/
protected function typeWithTabs(Browser $browser, string $selector, string $date, string $separator = '-')
{
$date = explode($separator, $date);
array_map(function ($group) use ($date, $browser, $selector) {
if (strtolower($group) === 'am') {
$browser->type($selector, 'a');
} elseif (strtolower($group) === 'pm') {
$browser->type($selector, 'p');
} else {
$browser->type($selector, $group);
}
// if the item is not the last in the array, let's tab through
if ($group !== end($date)) {
$browser->keys($selector, ['{tab}']);
}
}, $date);
}
/**
* Assert active modal is present.
*
* @return void
*/
public function assertPresentModal(Browser $browser)
{
$browser->assertPresent('.modal[data-modal-open=true]');
}
/**
* Assert active modal is missing.
*
* @return void
*/
public function assertMissingModal(Browser $browser)
{
$browser->assertMissing('.modal[data-modal-open=true]');
}
}

View File

@@ -0,0 +1,48 @@
<?php
namespace Laravel\Nova\Testing\Browser\Concerns;
use Laravel\Dusk\Browser;
use Laravel\Nova\Testing\Browser\Components\Modals\CreateRelationModalComponent;
trait InteractsWithInlineCreateRelation
{
/**
* Run the inline relation.
*
* @param \Laravel\Dusk\Browser $browser
* @param string $uriKey
* @param callable $fieldCallback
* @return void
*
* @throws \Facebook\WebDriver\Exception\TimeOutException
*/
public function showInlineCreate(Browser $browser, string $uriKey, callable $fieldCallback)
{
$browser->whenAvailable("@{$uriKey}-inline-create", function ($browser) use ($fieldCallback) {
$browser->click('')
->elsewhereWhenAvailable(new CreateRelationModalComponent(), function ($browser) use ($fieldCallback) {
$fieldCallback($browser);
});
});
}
/**
* Run the inline create relation.
*
* @param \Laravel\Dusk\Browser $browser
* @param string $uriKey
* @param callable $fieldCallback
* @return void
*
* @throws \Facebook\WebDriver\Exception\TimeOutException
*/
public function runInlineCreate(Browser $browser, string $uriKey, callable $fieldCallback)
{
$this->showInlineCreate($browser, $uriKey, function ($browser) use ($fieldCallback) {
$fieldCallback($browser);
$browser->click('@create-button')->pause(250);
});
}
}

View File

@@ -0,0 +1,158 @@
<?php
namespace Laravel\Nova\Testing\Browser\Pages;
use Laravel\Dusk\Browser;
use Laravel\Nova\Testing\Browser\Components\SearchInputComponent;
class Attach extends Page
{
use InteractsWithRelations;
public $resourceName;
public $resourceId;
public $relation;
public $viaRelationship;
public $polymorphic = false;
/**
* Create a new page instance.
*
* @param string $resourceName
* @param string $resourceId
* @param string $relation
* @param string|null $viaRelationship
* @param bool $polymorphic
* @return void
*/
public function __construct($resourceName, $resourceId, $relation, $viaRelationship = null, $polymorphic = false)
{
$this->relation = $relation;
$this->resourceId = $resourceId;
$this->resourceName = $resourceName;
$this->viaRelationship = $viaRelationship;
$this->polymorphic = $polymorphic;
$this->setNovaPage("/resources/{$this->resourceName}/{$this->resourceId}/attach/{$this->relation}");
}
/**
* Create a new page instance for Belongs-to-Many.
*
* @param string $resourceName
* @param string $resourceId
* @param string $relation
* @param string|null $viaRelationship
* @return static
*/
public static function belongsToMany($resourceName, $resourceId, $relation, $viaRelationship = null)
{
return new static($resourceName, $resourceId, $relation, $viaRelationship);
}
/**
* Create a new page instance for Morph-to-Many.
*
* @param string $resourceName
* @param string $resourceId
* @param string $relation
* @param string|null $viaRelationship
* @return static
*/
public static function morphToMany($resourceName, $resourceId, $relation, $viaRelationship = null)
{
return new static($resourceName, $resourceId, $relation, $viaRelationship, true);
}
/**
* Get the URL for the page.
*
* @return string
*/
public function url()
{
return $this->novaPageUrl.'?'.http_build_query([
'viaRelationship' => $this->viaRelationship ?? $this->relation,
'polymorphic' => $this->polymorphic === true ? 1 : 0,
]);
}
/**
* Select the attachable resource with the given ID.
*
* @param \Laravel\Dusk\Browser $browser
* @param string|int $id
* @return void
*/
public function selectAttachable(Browser $browser, $id)
{
$this->selectRelation($browser, 'attachable', $id);
}
/**
* Select the attachable resource with the given ID.
*
* @param \Laravel\Dusk\Browser $browser
* @param string|int $id
* @return void
*/
public function searchAttachable(Browser $browser, $id)
{
$browser->within(new SearchInputComponent($this->relation), function ($browser) use ($id) {
$browser->searchFirstRelation($id);
});
}
/**
* Click the attach button.
*
* @param \Laravel\Dusk\Browser $browser
* @return void
*/
public function create(Browser $browser)
{
$browser->dismissToasted()
->click('@attach-button')
->pause(1000);
}
/**
* Click the update and continue editing button.
*
* @param \Laravel\Dusk\Browser $browser
* @return void
*/
public function createAndAttachAnother(Browser $browser)
{
$browser->dismissToasted()
->click('@attach-and-attach-another-button')
->pause(750);
}
/**
* Click the cancel button.
*
* @param \Laravel\Dusk\Browser $browser
* @return void
*/
public function cancel(Browser $browser)
{
$browser->dismissToasted()
->click('@cancel-attach-button');
}
/**
* Assert that the browser is on the page.
*
* @param \Laravel\Dusk\Browser $browser
* @return void
*/
public function assert(Browser $browser)
{
$browser->assertOk()->waitFor('@nova-form');
}
}

View File

@@ -0,0 +1,88 @@
<?php
namespace Laravel\Nova\Testing\Browser\Pages;
use Laravel\Dusk\Browser;
class Create extends Page
{
use InteractsWithRelations;
public $resourceName;
/**
* Create a new page instance.
*
* @param string $resourceName
* @param array $queryParams
* @return void
*/
public function __construct($resourceName, $queryParams = [])
{
$this->resourceName = $resourceName;
$this->queryParams = $queryParams;
$this->setNovaPage("/resources/{$this->resourceName}/new");
}
/**
* Click the create button.
*
* @param \Laravel\Dusk\Browser $browser
* @return void
*/
public function create(Browser $browser)
{
$browser->dismissToasted()
->click('@create-button')
->pause(1000);
}
/**
* Click the create and add another button.
*
* @param \Laravel\Dusk\Browser $browser
* @return void
*/
public function createAndAddAnother(Browser $browser)
{
$browser->dismissToasted()
->click('@create-and-add-another-button')
->pause(500);
}
/**
* Click the cancel button.
*
* @param \Laravel\Dusk\Browser $browser
* @return void
*/
public function cancel(Browser $browser)
{
$browser->dismissToasted()
->click('@cancel-create-button');
}
/**
* Assert that the browser is on the page.
*
* @param \Laravel\Dusk\Browser $browser
* @return void
*/
public function assert(Browser $browser)
{
$browser->assertOk()->waitFor('@nova-form');
}
/**
* Assert that there are no search results.
*
* @param \Laravel\Dusk\Browser $browser
* @param string $resourceName
* @return void
*/
public function assertNoRelationSearchResults(Browser $browser, $resourceName)
{
$browser->assertMissing('@'.$resourceName.'-search-input-result-0');
}
}

View File

@@ -0,0 +1,46 @@
<?php
namespace Laravel\Nova\Testing\Browser\Pages;
use Laravel\Dusk\Browser;
class Dashboard extends Page
{
public $dashboardName;
/**
* Create a new page instance.
*
* @param string $dashboardName
* @return void
*/
public function __construct($dashboardName = 'main')
{
$this->dashboardName = $dashboardName;
$this->setNovaPage("/dashboards/{$this->dashboardName}");
}
/**
* Assert that the browser is on the page.
*
* @param \Laravel\Dusk\Browser $browser
* @return void
*/
public function assert(Browser $browser)
{
$browser->assertOk()->waitFor('@nova-dashboard');
}
/**
* Get the element shortcuts for the page.
*
* @return array
*/
public function elements()
{
return [
'@nova-dashboard' => "[dusk='dashboard-{$this->dashboardName}']",
];
}
}

View File

@@ -0,0 +1,242 @@
<?php
namespace Laravel\Nova\Testing\Browser\Pages;
use Laravel\Dusk\Browser;
use Laravel\Nova\Testing\Browser\Components\ActionDropdownComponent;
use Laravel\Nova\Testing\Browser\Components\IndexComponent;
use Laravel\Nova\Testing\Browser\Components\Modals\DeleteResourceModalComponent;
use Laravel\Nova\Testing\Browser\Components\Modals\RestoreResourceModalComponent;
class Detail extends Page
{
public $resourceName;
public $resourceId;
/**
* Create a new page instance.
*
* @param string $resourceName
* @param string $resourceId
* @param array $queryParams
* @return void
*/
public function __construct($resourceName, $resourceId, $queryParams = [])
{
$this->resourceId = $resourceId;
$this->resourceName = $resourceName;
$this->queryParams = $queryParams;
$this->setNovaPage("/resources/{$this->resourceName}/{$this->resourceId}");
}
/**
* Run the action with the given URI key.
*
* @param \Laravel\Dusk\Browser $browser
* @param string $uriKey
* @return void
*
* @throws \Facebook\WebDriver\Exception\TimeOutException
*/
public function runAction(Browser $browser, $uriKey)
{
$browser->openControlSelector()
->elsewhereWhenAvailable(new ActionDropdownComponent(), function ($browser) use ($uriKey) {
$browser->runWithConfirmation($uriKey);
});
}
/**
* Run the action with the given URI key.
*
* @param \Laravel\Dusk\Browser $browser
* @param string $uriKey
* @return void
*
* @throws \Facebook\WebDriver\Exception\TimeOutException
*/
public function runInstantAction(Browser $browser, $uriKey)
{
$browser->openControlSelector()
->elsewhereWhenAvailable(new ActionDropdownComponent(), function ($browser) use ($uriKey) {
$browser->runWithoutConfirmation($uriKey);
});
}
/**
* Open the action modal but cancel the action.
*
* @param \Laravel\Dusk\Browser $browser
* @param string $uriKey
* @return void
*
* @throws \Facebook\WebDriver\Exception\TimeOutException
*/
public function cancelAction(Browser $browser, $uriKey)
{
$browser->openControlSelector()
->elsewhereWhenAvailable(new ActionDropdownComponent(), function ($browser) use ($uriKey) {
$browser->cancel($uriKey);
});
}
/**
* Edit the resource.
*
* @param \Laravel\Dusk\Browser $browser
* @return void
*
* @throws \Facebook\WebDriver\Exception\TimeOutException
*/
public function edit(Browser $browser)
{
$browser->waitFor('@edit-resource-button')
->click('@edit-resource-button');
}
/**
* Create the related resource.
*
* @param \Laravel\Dusk\Browser $browser
* @param string $relatedResourceName
* @return void
*
* @throws \Facebook\WebDriver\Exception\TimeOutException
*/
public function runCreateRelation(Browser $browser, $relatedResourceName)
{
$browser->within(new IndexComponent($relatedResourceName), function ($browser) {
$browser->waitFor('@create-button')->click('@create-button');
})->on(new Create($relatedResourceName));
}
/**
* Create the related resource.
*
* @param \Laravel\Dusk\Browser $browser
* @param string $relatedResourceName
* @param string|null $viaRelationship
* @return void
*
* @throws \Facebook\WebDriver\Exception\TimeOutException
*/
public function runAttachRelation(Browser $browser, $relatedResourceName, $viaRelationship = null)
{
$browser->within(new IndexComponent($relatedResourceName, $viaRelationship), function ($browser) {
$browser->waitFor('@attach-button')->click('@attach-button');
})->on(new Attach($this->resourceName, $this->resourceId, $relatedResourceName, $viaRelationship));
}
/**
* Open the delete selector.
*
* @param \Laravel\Dusk\Browser $browser
* @return void
*
* @throws \Facebook\WebDriver\Exception\TimeOutException
*/
public function openControlSelector(Browser $browser)
{
$browser->whenAvailable("@{$this->resourceId}-control-selector", function ($browser) {
$browser->click('');
})->pause(100);
}
/**
* Replicate the resource.
*
* @param \Laravel\Dusk\Browser $browser
* @return void
*
* @throws \Facebook\WebDriver\Exception\TimeOutException
*/
public function replicate(Browser $browser)
{
$browser->openControlSelector()
->whenAvailable("@{$this->resourceId}-replicate-button", function ($browser) {
$browser->click('');
});
}
/**
* Delete the resource.
*
* @param \Laravel\Dusk\Browser $browser
* @return void
*
* @throws \Facebook\WebDriver\Exception\TimeOutException
*/
public function delete(Browser $browser)
{
$browser->openControlSelector()
->whenAvailable('@open-delete-modal-button', function ($browser) {
$browser->click('');
})
->elsewhereWhenAvailable(new DeleteResourceModalComponent(), function ($browser) {
$browser->confirm();
})->pause(1000);
}
/**
* Restore the resource.
*
* @param \Laravel\Dusk\Browser $browser
* @return void
*
* @throws \Facebook\WebDriver\Exception\TimeOutException
*/
public function restore(Browser $browser)
{
$browser->openControlSelector()
->whenAvailable('@open-restore-modal-button', function ($browser) {
$browser->click('');
})
->elsewhereWhenAvailable(new RestoreResourceModalComponent(), function ($browser) {
$browser->confirm();
})->pause(1000);
}
/**
* Force delete the resource.
*
* @param \Laravel\Dusk\Browser $browser
* @return void
*
* @throws \Facebook\WebDriver\Exception\TimeOutException
*/
public function forceDelete(Browser $browser)
{
$browser->openControlSelector()
->whenAvailable('@open-force-delete-modal-button', function ($browser) {
$browser->click('');
})
->elsewhereWhenAvailable(new DeleteResourceModalComponent(), function ($browser) {
$browser->confirm();
})->pause(1000);
}
/**
* Assert that the browser is on the page.
*
* @param \Laravel\Dusk\Browser $browser
* @return void
*/
public function assert(Browser $browser)
{
$browser->assertOk()->waitFor('@nova-resource-detail');
}
/**
* Get the element shortcuts for the page.
*
* @return array
*/
public function elements()
{
return [
'@nova-resource-detail' => '[dusk="'.$this->resourceName.'-detail-component"]',
];
}
}

View File

@@ -0,0 +1,31 @@
<?php
namespace Laravel\Nova\Testing\Browser\Pages;
use Laravel\Dusk\Browser;
class Forbidden extends Page
{
/**
* Create a new page instance.
*
* @return void
*/
public function __construct()
{
parent::__construct('/403');
}
/**
* Assert that the browser is on the page.
*
* @param \Laravel\Dusk\Browser $browser
* @return void
*/
public function assert(Browser $browser)
{
$browser->whenAvailable('@403-error-page', function ($browser) {
$browser->assertSee('403');
});
}
}

View File

@@ -0,0 +1,214 @@
<?php
namespace Laravel\Nova\Testing\Browser\Pages;
use Laravel\Dusk\Browser;
use Laravel\Nova\Testing\Browser\Components\SearchInputComponent;
trait HasSearchable
{
/**
* Search for the given value for a searchable field attribute.
*
* @param \Laravel\Dusk\Browser $browser
* @param string $attribute
* @param string $search
* @param int $pause
* @return void
*/
public function searchInput(Browser $browser, $attribute, $search, int $pause = 500)
{
$browser->whenAvailable(new SearchInputComponent($attribute), function ($browser) use ($search, $pause) {
$browser->searchInput($search, $pause);
});
}
/**
* Reset the searchable field.
*
* @param \Laravel\Dusk\Browser $browser
* @param string $attribute
* @param int $resultIndex
* @return void
*/
public function resetSearchResult(Browser $browser, $attribute)
{
$browser->whenAvailable(new SearchInputComponent($attribute), function ($browser) {
$browser->resetSearchResult();
});
}
/**
* Select the searchable field by result index.
*
* @param \Laravel\Dusk\Browser $browser
* @param string $attribute
* @param int $resultIndex
* @return void
*/
public function selectSearchResult(Browser $browser, $attribute, $resultIndex)
{
$browser->whenAvailable(new SearchInputComponent($attribute), function ($browser) use ($resultIndex) {
$browser->selectSearchResult($resultIndex);
});
}
/**
* Select the currently highlighted searchable field.
*
* @param \Laravel\Dusk\Browser $browser
* @param string $attribute
* @return void
*/
public function selectFirstSearchResult(Browser $browser, $attribute)
{
$this->selectSearchResult($browser, $attribute, 0);
}
/**
* Select the currently highlighted searchable field.
*
* @param \Laravel\Dusk\Browser $browser
* @param string $attribute
* @return void
*/
public function cancelSelectingSearchResult(Browser $browser, $attribute)
{
$browser->whenAvailable(new SearchInputComponent($attribute), function ($browser) {
$browser->cancelSelectingSearchResult();
});
}
/**
* Search and select the searchable field by result index.
*
* @param \Laravel\Dusk\Browser $browser
* @param string $attribute
* @param string $search
* @param int $resultIndex
* @return void
*/
public function searchAndSelectResult(Browser $browser, $attribute, $search, $resultIndex)
{
$browser->whenAvailable(new SearchInputComponent($attribute), function ($browser) use ($search, $resultIndex) {
$browser->searchAndSelectResult($search, $resultIndex);
});
}
/**
* Search and select the currently highlighted searchable field.
*
* @param \Laravel\Dusk\Browser $browser
* @param string $attribute
* @param string $search
* @return void
*/
public function searchAndSelectFirstResult(Browser $browser, $attribute, $search)
{
$this->searchAndSelectResult($browser, $attribute, $search, 0);
}
/**
* Assert on searchable results.
*
* @param \Laravel\Dusk\Browser $browser
* @param string $attribute
* @param callable(\Laravel\Nova\Browser, string):void $fieldCallback
* @return void
*/
public function assertSearchResult(Browser $browser, $attribute, callable $fieldCallback)
{
$browser->whenAvailable(new SearchInputComponent($attribute), function ($browser) use ($fieldCallback) {
$browser->assertSearchResult($search, $fieldCallback);
});
}
/**
* Assert on searchable results current value.
*
* @param \Laravel\Dusk\Browser $browser
* @param string $attribute
* @param string $search
* @return void
*/
public function assertSelectedSearchResult(Browser $browser, $attribute, $search)
{
$browser->whenAvailable(new SearchInputComponent($attribute), function ($browser) use ($search) {
$browser->assertSelectedSearchResult($search);
});
}
/**
* Assert on searchable results is locked to single result.
*
* @param \Laravel\Dusk\Browser $browser
* @param string $attribute
* @param string $search
* @return void
*/
public function assertSelectedFirstSearchResult(Browser $browser, $attribute, $search)
{
$browser->whenAvailable(new SearchInputComponent($attribute), function ($browser) use ($search) {
$browser->assertSelectedFirstSearchResult($search);
});
}
/**
* Assert on searchable results is empty.
*
* @param \Laravel\Dusk\Browser $browser
* @param string $attribute
* @return void
*/
public function assertEmptySearchResult(Browser $browser, $attribute)
{
$browser->whenAvailable(new SearchInputComponent($attribute), function ($browser) {
$browser->assertEmptySearchResult();
});
}
/**
* Assert on searchable results has the search value.
*
* @param \Laravel\Dusk\Browser $browser
* @param string $attribute
* @param string|array $search
* @return void
*
* @deprecated
*/
public function assertSearchResultHas(Browser $browser, $attribute, $search)
{
$this->assertSearchResultContains($browser, $attribute, $search);
}
/**
* Assert on searchable results has the search value.
*
* @param \Laravel\Dusk\Browser $browser
* @param string $attribute
* @param string|array $search
* @return void
*/
public function assertSearchResultContains(Browser $browser, $attribute, $search)
{
$browser->whenAvailable(new SearchInputComponent($attribute), function ($browser) use ($search) {
$browser->assertSearchResultContains($search);
});
}
/**
* Assert on searchable results doesn't has the search value.
*
* @param \Laravel\Dusk\Browser $browser
* @param string $attribute
* @param string|array $search
* @return void
*/
public function assertSearchResultDoesNotContains(Browser $browser, $attribute, $search)
{
$browser->whenAvailable(new SearchInputComponent($attribute), function ($browser) use ($search) {
$browser->assertSearchResultDoesNotContains($search);
});
}
}

View File

@@ -0,0 +1,16 @@
<?php
namespace Laravel\Nova\Testing\Browser\Pages;
class HomePage extends Page
{
/**
* Create a new page instance.
*
* @return void
*/
public function __construct()
{
parent::__construct('/');
}
}

View File

@@ -0,0 +1,72 @@
<?php
namespace Laravel\Nova\Testing\Browser\Pages;
use Laravel\Dusk\Browser;
use Laravel\Nova\Testing\Browser\Components\FormComponent;
use Laravel\Nova\Testing\Browser\Components\IndexComponent;
class Index extends Page
{
public $resourceName;
/**
* Create a new page instance.
*
* @param string $resourceName
* @param array $queryParams
* @return void
*/
public function __construct($resourceName, $queryParams = [])
{
$this->resourceName = $resourceName;
$this->queryParams = $queryParams;
$this->setNovaPage("/resources/{$this->resourceName}");
}
/**
* Create the related resource.
*
* @param \Laravel\Dusk\Browser $browser
* @param \Closure|null $fieldCallback
* @return void
*
* @throws \Facebook\WebDriver\Exception\TimeOutException
*/
public function runCreate(Browser $browser, $fieldCallback = null)
{
$browser->within(new IndexComponent($this->resourceName), function ($browser) {
$browser->waitFor('@create-button')->click('@create-button');
})->on(new Create($this->resourceName));
if (! is_null($fieldCallback)) {
$browser->within(new FormComponent(), function ($browser) use ($fieldCallback) {
call_user_func($fieldCallback, $browser);
});
}
}
/**
* Assert that the browser is on the page.
*
* @param \Laravel\Dusk\Browser $browser
* @return void
*/
public function assert(Browser $browser)
{
$browser->assertOk()->waitFor('@nova-resource-index');
}
/**
* Get the element shortcuts for the page.
*
* @return array
*/
public function elements()
{
return [
'@nova-resource-index' => '[dusk="'.$this->resourceName.'-index-component"]',
];
}
}

View File

@@ -0,0 +1,147 @@
<?php
namespace Laravel\Nova\Testing\Browser\Pages;
use Laravel\Dusk\Browser;
use Laravel\Nova\Testing\Browser\Components\Controls\RelationSelectControlComponent;
use Laravel\Nova\Testing\Browser\Concerns\InteractsWithInlineCreateRelation;
trait InteractsWithRelations
{
use HasSearchable;
use InteractsWithInlineCreateRelation;
/**
* Select for the given value for a relationship attribute.
*
* @param \Laravel\Dusk\Browser $browser
* @param string $attribute
* @param string|null $value
* @return void
*/
public function selectRelation(Browser $browser, $attribute, $value = null)
{
$browser->whenAvailable(new RelationSelectControlComponent($attribute), function (Browser $browser) use ($value) {
$browser->assertSelectHasOption('', $value)->select('', $value);
});
}
/**
* Search for the given value for a searchable relationship attribute.
*
* @param \Laravel\Dusk\Browser $browser
* @param string $attribute
* @param string $search
* @return void
*/
public function searchRelation(Browser $browser, $attribute, $search)
{
$this->searchInput($browser, $attribute, $search);
}
/**
* Reset the searchable relationship attribute.
*
* @param \Laravel\Dusk\Browser $browser
* @param string $attribute
* @return void
*/
public function resetSearchRelation(Browser $browser, $attribute)
{
$this->resetSearchResult($browser, $attribute);
}
/**
* Select the currently highlighted searchable relation.
*
* @param \Laravel\Dusk\Browser $browser
* @param string $attribute
* @return void
*
* @deprecated
*/
public function selectFirstRelation(Browser $browser, $attribute)
{
$this->selectFirstSearchResult($browser, $attribute);
}
/**
* Select the currently highlighted searchable relation.
*
* @param \Laravel\Dusk\Browser $browser
* @param string $attribute
* @return void
*/
public function firstSearchableResult(Browser $browser, $attribute)
{
$this->selectFirstSearchResult($browser, $attribute);
}
/**
* Close the searchable relation result.
*
* @param \Laravel\Dusk\Browser $browser
* @param string $attribute
* @return void
*
* @deprecated
*/
public function closeSearchableResult(Browser $browser, $attribute)
{
$this->cancelSelectingSearchResult($browser, $attribute);
}
/**
* Search and select the currently highlighted searchable relation.
*
* @param \Laravel\Dusk\Browser $browser
* @param string $attribute
* @param string $search
* @return void
*/
public function searchFirstRelation(Browser $browser, $attribute, $search)
{
$this->searchAndSelectFirstResult($browser, $attribute, $search);
}
/**
* Indicate that trashed relations should be included in the search results.
*
* @param \Laravel\Dusk\Browser $browser
* @param string $resourceName
* @return void
*/
public function waitForTrashedRelation(Browser $browser, $resourceName)
{
$browser->waitFor("@{$resourceName}-with-trashed-checkbox", 5);
}
/**
* Indicate that trashed relations should be included in the search results.
*/
public function withTrashedRelation(Browser $browser, $resourceName)
{
$browser->waitForTrashedRelation($resourceName)->click('')->with(
"@{$resourceName}-with-trashed-checkbox",
function (Browser $browser) {
$browser->waitFor('input[type="checkbox"]')
->check('input[type="checkbox"]')
->pause(250);
}
);
}
/**
* Indicate that trashed relations should not be included in the search results.
*
* @param \Laravel\Dusk\Browser $browser
* @param string $resourceName
* @return void
*/
public function withoutTrashedRelation(Browser $browser, $resourceName)
{
$browser->waitForTrashedRelation($resourceName)
->uncheck('[dusk="'.$resourceName.'-with-trashed-checkbox"] input[type="checkbox"]')
->pause(250);
}
}

View File

@@ -0,0 +1,50 @@
<?php
namespace Laravel\Nova\Testing\Browser\Pages;
use Laravel\Dusk\Browser;
class Lens extends Index
{
public $lens;
/**
* Create a new page instance.
*
* @param string $resourceName
* @param string $lens
* @param array $queryParams
* @return void
*/
public function __construct($resourceName, $lens, $queryParams = [])
{
$this->lens = $lens;
$this->resourceName = $resourceName;
$this->queryParams = $queryParams;
$this->setNovaPage("/resources/{$this->resourceName}/lens/{$this->lens}");
}
/**
* Assert that the browser is on the page.
*
* @param \Laravel\Dusk\Browser $browser
* @return void
*/
public function assert(Browser $browser)
{
$browser->assertOk()->waitFor('@nova-resource-lens');
}
/**
* Get the element shortcuts for the page.
*
* @return array
*/
public function elements()
{
return [
'@nova-resource-lens' => '[dusk="'.$this->lens.'-lens-component"]',
];
}
}

View File

@@ -0,0 +1,40 @@
<?php
namespace Laravel\Nova\Testing\Browser\Pages;
use Laravel\Dusk\Browser;
class Login extends Page
{
/**
* Create a new page instance.
*
* @return void
*/
public function __construct()
{
parent::__construct('/login');
}
/**
* Assert that the browser is on the page.
*
* @param \Laravel\Dusk\Browser $browser
* @return void
*/
public function assert(Browser $browser)
{
$browser->assertOk();
}
/**
* Assert page not found.
*
* @param \Laravel\Dusk\Browser $browser
* @return void
*/
public function assertOk(Browser $browser)
{
$browser->waitForLocation($this->novaPageUrl)->assertPathIs($this->novaPageUrl);
}
}

View File

@@ -0,0 +1,31 @@
<?php
namespace Laravel\Nova\Testing\Browser\Pages;
use Laravel\Dusk\Browser;
class NotFound extends Page
{
/**
* Create a new page instance.
*
* @return void
*/
public function __construct()
{
parent::__construct('/404');
}
/**
* Assert that the browser is on the page.
*
* @param \Laravel\Dusk\Browser $browser
* @return void
*/
public function assert(Browser $browser)
{
$browser->whenAvailable('@404-error-page', function ($browser) {
$browser->assertSee('404');
});
}
}

View File

@@ -0,0 +1,124 @@
<?php
namespace Laravel\Nova\Testing\Browser\Pages;
use Laravel\Dusk\Browser;
use Laravel\Dusk\Page as Dusk;
use Laravel\Nova\Nova;
use Laravel\Nova\Testing\Browser\Concerns\InteractsWithElements;
class Page extends Dusk
{
use InteractsWithElements;
public $novaPageUrl;
public $queryParams;
/**
* Create a new page instance.
*
* @param string $path
* @return void
*/
public function __construct($path = '/')
{
$this->setNovaPage($path);
}
/**
* Get the URL for the page.
*
* @return string
*/
public function url()
{
if ($this->queryParams) {
return $this->novaPageUrl.'?'.http_build_query($this->queryParams);
}
return $this->novaPageUrl;
}
/**
* Assert page not found.
*
* @param \Laravel\Dusk\Browser $browser
* @return void
*/
public function assertOk(Browser $browser)
{
$browser->waitForLocation($this->novaPageUrl)
->assertPathIs($this->novaPageUrl)
->waitFor('@nova-content');
}
/**
* Assert page not found.
*
* @param \Laravel\Dusk\Browser $browser
* @return void
*/
public function assertNotFound(Browser $browser)
{
$browser->on(new NotFound());
}
/**
* Assert page not forbidden.
*
* @param \Laravel\Dusk\Browser $browser
* @return void
*/
public function assertForbidden(Browser $browser)
{
$browser->on(new Forbidden());
}
/**
* Assert page doesn't contain breadcrumb.
*
* @param \Laravel\Dusk\Browser $browser
* @return void
*/
public function assertWithoutBreadcrumb(Browser $browser)
{
$browser->assertMissing('@breadcrumbs');
}
/**
* Set luxon timezone for the frontend.
*
* @param \Laravel\Dusk\Browser $browser
* @param string $timezone
* @return void
*/
public function luxonTimezone(Browser $browser, string $timezone = 'system')
{
$browser->script('Nova.$testing.timezone("'.$timezone.'")');
}
/**
* Get the global element shortcuts for the site.
*
* @return array
*/
public static function siteElements(): array
{
return [
'@nova-content' => '[dusk="content"]',
'@nova-form' => '[dusk="content"]',
];
}
/**
* Set Nova Page URL.
*
* @param string $path
* @return void
*/
protected function setNovaPage(string $path)
{
$this->novaPageUrl = Nova::path().'/'.trim($path, '/');
}
}

View File

@@ -0,0 +1,25 @@
<?php
namespace Laravel\Nova\Testing\Browser\Pages;
class Replicate extends Create
{
public $fromResourceId;
/**
* Create a new page instance.
*
* @param string $resourceName
* @param int|string $fromResourceId
* @param array $queryParams
* @return void
*/
public function __construct($resourceName, $fromResourceId, $queryParams = [])
{
parent::__construct($resourceName, $queryParams);
$this->fromResourceId = $fromResourceId;
$this->setNovaPage("/resources/{$this->resourceName}/{$this->fromResourceId}/replicate");
}
}

View File

@@ -0,0 +1,111 @@
<?php
namespace Laravel\Nova\Testing\Browser\Pages;
use Laravel\Dusk\Browser;
use Laravel\Nova\Testing\Browser\Components\Modals\CreateRelationModalComponent;
class Update extends Page
{
use InteractsWithRelations;
public $resourceName;
public $resourceId;
public $queryParams;
/**
* Create a new page instance.
*
* @param string $resourceName
* @param int $resourceId
* @param array $queryParams
* @return void
*/
public function __construct($resourceName, $resourceId, $queryParams = [])
{
$this->resourceName = $resourceName;
$this->resourceId = $resourceId;
$this->queryParams = $queryParams;
$this->setNovaPage("/resources/{$this->resourceName}/{$this->resourceId}/edit");
}
/**
* Run the inline create relation.
*
* @param \Laravel\Dusk\Browser $browser
* @param string $uriKey
* @param callable $fieldCallback
* @return void
*
* @throws \Facebook\WebDriver\Exception\TimeOutException
*/
public function runInlineCreate(Browser $browser, $uriKey, callable $fieldCallback)
{
$browser->whenAvailable("@{$uriKey}-inline-create", function ($browser) use ($fieldCallback) {
$browser->click('')
->elsewhereWhenAvailable(new CreateRelationModalComponent(), function ($browser) use ($fieldCallback) {
$fieldCallback($browser);
$browser->confirm()->pause(250);
});
});
}
/**
* Click the update button.
*
* @param \Laravel\Dusk\Browser $browser
* @return void
*
* @throws \Facebook\WebDriver\Exception\TimeOutException
*/
public function update(Browser $browser)
{
$browser->dismissToasted()
->waitFor('@update-button')
->click('@update-button')
->pause(500);
}
/**
* Click the update and continue editing button.
*
* @param \Laravel\Dusk\Browser $browser
* @return void
*
* @throws \Facebook\WebDriver\Exception\TimeOutException
*/
public function updateAndContinueEditing(Browser $browser)
{
$browser->dismissToasted()
->waitFor('@update-and-continue-editing-button')
->click('@update-and-continue-editing-button')
->pause(500);
}
/**
* Click the cancel button.
*
* @param \Laravel\Dusk\Browser $browser
* @return void
*/
public function cancel(Browser $browser)
{
$browser->dismissToasted()
->click('@cancel-update-button');
}
/**
* Assert that the browser is on the page.
*
* @param \Laravel\Dusk\Browser $browser
* @return void
*/
public function assert(Browser $browser)
{
$browser->assertOk()->waitFor('@nova-form');
}
}

View File

@@ -0,0 +1,139 @@
<?php
namespace Laravel\Nova\Testing\Browser\Pages;
use Laravel\Dusk\Browser;
class UpdateAttached extends Page
{
public $resourceName;
public $resourceId;
public $relation;
public $relatedId;
public $viaRelationship;
public $viaPivotId;
/**
* Create a new page instance.
*
* @param string $resourceName
* @param string $resourceId
* @param string $relation
* @param string $relatedId
* @param string|null $viaRelationship
* @param string|null $viaPivotId
* @return void
*/
public function __construct($resourceName, $resourceId, $relation, $relatedId, $viaRelationship = null, $viaPivotId = null)
{
$this->relation = $relation;
$this->relatedId = $relatedId;
$this->resourceId = $resourceId;
$this->resourceName = $resourceName;
$this->viaRelationship = $viaRelationship;
$this->viaPivotId = $viaPivotId;
$this->setNovaPage("/resources/{$this->resourceName}/{$this->resourceId}/edit-attached/{$this->relation}/{$this->relatedId}");
}
/**
* Create a new page instance for Belongs-to-Many.
*
* @param string $resourceName
* @param string $resourceId
* @param string $relation
* @param string $relatedId
* @param string|null $viaRelationship
* @param string|null $viaPivotId
* @return static
*/
public static function belongsToMany($resourceName, $resourceId, $relation, $relatedId, $viaRelationship = null, $viaPivotId = null)
{
return new static($resourceName, $resourceId, $relation, $relatedId, $viaRelationship, $viaPivotId);
}
/**
* Create a new page instance for Morph-to-Many.
*
* @param string $resourceName
* @param string $resourceId
* @param string $relation
* @param string $relatedId
* @param string|null $viaRelationship
* @param string|null $viaPivotId
* @return static
*/
public static function morphToMany($resourceName, $resourceId, $relation, $relatedId, $viaRelationship = null, $viaPivotId = null)
{
return new static($resourceName, $resourceId, $relation, $relatedId, $viaRelationship, $viaPivotId);
}
/**
* Get the URL for the page.
*
* @return string
*/
public function url()
{
return $this->novaPageUrl.'?'.http_build_query(array_filter([
'viaRelationship' => $this->viaRelationship ?? $this->relation,
'viaPivotId' => $this->viaPivotId,
]));
}
/**
* Click the update button.
*
* @param \Laravel\Dusk\Browser $browser
* @return void
*/
public function update(Browser $browser)
{
$browser->dismissToasted()
->click('@update-button')
->pause(750);
}
/**
* Click the update and continue editing button.
*
* @param \Laravel\Dusk\Browser $browser
* @return void
*/
public function updateAndContinueEditing(Browser $browser)
{
$browser->dismissToasted()
->click('@update-and-continue-editing-button')
->pause(750);
}
/**
* Click the cancel button.
*
* @param \Laravel\Dusk\Browser $browser
* @return void
*/
public function cancel(Browser $browser)
{
$browser->dismissToasted()
->click('@cancel-update-attached-button');
}
/**
* Assert that the browser is on the page.
*
* @param \Laravel\Dusk\Browser $browser
* @return void
*
* @throws \Facebook\WebDriver\Exception\TimeOutException
*/
public function assert(Browser $browser)
{
$browser->assertOk()->waitFor('@nova-form');
}
}

View File

@@ -0,0 +1,17 @@
<?php
namespace Laravel\Nova\Testing\Browser\Pages;
class UserIndex extends Index
{
/**
* Create a new page instance.
*
* @param array $queryParams
* @return void
*/
public function __construct($queryParams = [])
{
parent::__construct('users', $queryParams);
}
}

View File

@@ -0,0 +1,22 @@
<?php
namespace Laravel\Nova\Testing\Concerns;
use Illuminate\Foundation\Testing\Concerns\MakesHttpRequests as IlluminateMakesHttpRequests;
use Laravel\Nova\Http\Requests\NovaRequest;
trait MakesHttpRequests
{
use IlluminateMakesHttpRequests;
/**
* Create the request instance used for testing from the given Symfony request.
*
* @param \Symfony\Component\HttpFoundation\Request $symfonyRequest
* @return \Laravel\Nova\Http\Requests\NovaRequest
*/
protected function createTestRequest($symfonyRequest)
{
return NovaRequest::createFromBase($symfonyRequest);
}
}