Repo rebuild
This commit is contained in:
@@ -0,0 +1,303 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Modules\Helpdesk\Tests\Browser;
|
||||
|
||||
use Laravel\Dusk\Browser;
|
||||
use Modules\Helpdesk\Models\Attachment;
|
||||
use Modules\Helpdesk\Models\Ticket;
|
||||
use Modules\Helpdesk\Tests\Concerns\BuildsHelpdeskFixtures;
|
||||
use Tests\DuskTestCase;
|
||||
|
||||
final class HelpdeskCreateTicketAttachmentBrowserTest extends DuskTestCase
|
||||
{
|
||||
use BuildsHelpdeskFixtures;
|
||||
|
||||
public function test_user_can_create_ticket_with_attachment_in_browser(): void
|
||||
{
|
||||
$fixtures = $this->seedLookupData();
|
||||
$user = $this->user(['name' => 'Browser Attachment User']);
|
||||
$subject = 'Browser attachment ticket ' . uniqid('', true);
|
||||
$filePath = $this->makeBrowserUploadFile('browser-upload-proof.log', 'Browser upload proof file.');
|
||||
|
||||
$this->browse(function (Browser $browser) use ($user, $fixtures, $subject, $filePath): void {
|
||||
$browser->loginAs($user)
|
||||
->visitRoute('helpdesk.tickets.create')
|
||||
->waitFor('@helpdesk-ticket-create', 10)
|
||||
->assertSee('Create ticket')
|
||||
->type('@ticket-subject', $subject);
|
||||
|
||||
$browser->attach('#attachments', $filePath);
|
||||
|
||||
$this->waitForHelpdeskUploadToFinish($browser);
|
||||
|
||||
$browser->type('@ticket-subject', $subject);
|
||||
$this->setHelpdeskSelect($browser, 'board_id', $fixtures['board']->id);
|
||||
$this->setHelpdeskSelect($browser, 'priority_id', $fixtures['high']->id);
|
||||
$this->setHelpdeskSelect($browser, 'category_id', $fixtures['category']->id);
|
||||
$this->setHelpdeskSelect($browser, 'team_id', $fixtures['team']->id);
|
||||
$this->setHelpdeskSelect($browser, 'project_id', $fixtures['project']->id);
|
||||
$this->setHelpdeskEditor(
|
||||
$browser,
|
||||
'ticket-body-editor',
|
||||
'This browser ticket proves attachments can be uploaded and saved.',
|
||||
);
|
||||
|
||||
$this->submitTicketCreateComponent($browser);
|
||||
|
||||
$this->waitForTicketCreated($browser, $subject);
|
||||
|
||||
$browser->visitRoute('helpdesk.tickets.index')
|
||||
->waitForText($subject, 10)
|
||||
->assertPathIs('/helpdesk/tickets')
|
||||
->assertSee($subject);
|
||||
});
|
||||
|
||||
$ticket = Ticket::query()
|
||||
->where('subject', $subject)
|
||||
->firstOrFail();
|
||||
|
||||
$attachment = Attachment::query()
|
||||
->where('ticket_id', $ticket->id)
|
||||
->whereNull('comment_id')
|
||||
->where('original_name', 'browser-upload-proof.log')
|
||||
->firstOrFail();
|
||||
|
||||
self::assertSame($ticket->id, (int) $attachment->ticket_id);
|
||||
}
|
||||
|
||||
public function test_user_can_create_ticket_with_multiple_attachments_in_browser(): void
|
||||
{
|
||||
$fixtures = $this->seedLookupData();
|
||||
$user = $this->user(['name' => 'Browser Multi Attachment User']);
|
||||
$subject = 'Browser multiple attachment ticket ' . uniqid('', true);
|
||||
$firstFile = $this->makeBrowserUploadFile('browser-first-proof.txt', 'First browser upload proof file.');
|
||||
$secondFile = $this->makeBrowserUploadFile('browser-second-proof.csv', 'name,value' . PHP_EOL . 'proof,1');
|
||||
|
||||
$this->browse(function (Browser $browser) use ($user, $fixtures, $subject, $firstFile, $secondFile): void {
|
||||
$browser->loginAs($user)
|
||||
->visitRoute('helpdesk.tickets.create')
|
||||
->waitFor('@helpdesk-ticket-create', 10)
|
||||
->assertSee('Create ticket')
|
||||
->type('@ticket-subject', $subject);
|
||||
|
||||
$browser->attach('#attachments', $firstFile . "\n" . $secondFile);
|
||||
|
||||
$this->waitForHelpdeskUploadToFinish($browser);
|
||||
|
||||
$browser->type('@ticket-subject', $subject);
|
||||
$this->setHelpdeskSelect($browser, 'board_id', $fixtures['board']->id);
|
||||
$this->setHelpdeskSelect($browser, 'priority_id', $fixtures['normal']->id);
|
||||
$this->setHelpdeskSelect($browser, 'category_id', $fixtures['category']->id);
|
||||
$this->setHelpdeskSelect($browser, 'team_id', $fixtures['team']->id);
|
||||
$this->setHelpdeskSelect($browser, 'project_id', $fixtures['project']->id);
|
||||
$this->setHelpdeskEditor(
|
||||
$browser,
|
||||
'ticket-body-editor',
|
||||
'This browser ticket proves multiple attachments can be uploaded and saved.',
|
||||
);
|
||||
|
||||
$this->submitTicketCreateComponent($browser);
|
||||
|
||||
$this->waitForTicketCreated($browser, $subject);
|
||||
|
||||
$browser->visitRoute('helpdesk.tickets.index')
|
||||
->waitForText($subject, 10)
|
||||
->assertPathIs('/helpdesk/tickets')
|
||||
->assertSee($subject);
|
||||
});
|
||||
|
||||
$ticket = Ticket::query()
|
||||
->where('subject', $subject)
|
||||
->firstOrFail();
|
||||
|
||||
self::assertSame(
|
||||
2,
|
||||
Attachment::query()->where('ticket_id', $ticket->id)->whereNull('comment_id')->count(),
|
||||
);
|
||||
|
||||
foreach (['browser-first-proof.txt', 'browser-second-proof.csv'] as $name) {
|
||||
$this->assertDatabaseHas('helpdesk_attachments', [
|
||||
'ticket_id' => $ticket->id,
|
||||
'comment_id' => null,
|
||||
'original_name' => $name,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
private function makeBrowserUploadFile(string $name, string $contents): string
|
||||
{
|
||||
$directory = storage_path('framework/testing/helpdesk-browser-uploads');
|
||||
|
||||
if (! is_dir($directory)) {
|
||||
mkdir($directory, 0777, true);
|
||||
}
|
||||
|
||||
$path = $directory . DIRECTORY_SEPARATOR . $name;
|
||||
file_put_contents($path, $contents);
|
||||
|
||||
return $path;
|
||||
}
|
||||
|
||||
private function setHelpdeskEditor(Browser $browser, string $dusk, string $text): void
|
||||
{
|
||||
$encodedDusk = json_encode($dusk, JSON_THROW_ON_ERROR);
|
||||
$encodedText = json_encode($text, JSON_THROW_ON_ERROR);
|
||||
|
||||
$browser->script(<<<JS
|
||||
const key = {$encodedDusk};
|
||||
const text = {$encodedText};
|
||||
const root = document.querySelector('[dusk="' + key + '"], [data-dusk="' + key + '"]');
|
||||
|
||||
if (! root) {
|
||||
throw new Error('Helpdesk editor ' + key + ' was not found.');
|
||||
}
|
||||
|
||||
const editor = root.matches('textarea, input, [contenteditable="true"]')
|
||||
? root
|
||||
: root.querySelector('textarea, input, [contenteditable="true"]');
|
||||
|
||||
if (! editor) {
|
||||
throw new Error('Helpdesk editor content area ' + key + ' was not found.');
|
||||
}
|
||||
|
||||
editor.focus();
|
||||
|
||||
if (editor.matches('textarea, input')) {
|
||||
editor.value = text;
|
||||
} else {
|
||||
editor.textContent = text;
|
||||
}
|
||||
|
||||
editor.dispatchEvent(new InputEvent('input', {
|
||||
bubbles: true,
|
||||
composed: true,
|
||||
inputType: 'insertText',
|
||||
data: text,
|
||||
}));
|
||||
editor.dispatchEvent(new Event('change', { bubbles: true, composed: true }));
|
||||
editor.dispatchEvent(new Event('blur', { bubbles: true, composed: true }));
|
||||
|
||||
const model = root.getAttribute('wire:model')
|
||||
|| root.getAttribute('wire:model.live')
|
||||
|| root.getAttribute('wire:model.defer')
|
||||
|| root.getAttribute('data-wire-model')
|
||||
|| editor.getAttribute('data-wire-model')
|
||||
|| editor.getAttribute('data-helpdesk-editor-model')
|
||||
|| editor.getAttribute('data-helpdesk-sync-target');
|
||||
|
||||
let componentRoot = root;
|
||||
while (componentRoot && ! componentRoot.hasAttribute('wire:id')) {
|
||||
componentRoot = componentRoot.parentElement;
|
||||
}
|
||||
|
||||
if (model && componentRoot && window.Livewire) {
|
||||
const component = window.Livewire.find(componentRoot.getAttribute('wire:id'));
|
||||
|
||||
if (component) {
|
||||
component.set(model, text);
|
||||
}
|
||||
}
|
||||
JS);
|
||||
|
||||
$browser->pause(500);
|
||||
}
|
||||
|
||||
private function setHelpdeskSelect(Browser $browser, string $model, int|string|null $value): void
|
||||
{
|
||||
$encodedModel = json_encode($model, JSON_THROW_ON_ERROR);
|
||||
$encodedValue = json_encode($value === null ? '' : (string) $value, JSON_THROW_ON_ERROR);
|
||||
|
||||
$browser->script(<<<JS
|
||||
const model = {$encodedModel};
|
||||
const value = {$encodedValue};
|
||||
const select = document.querySelector('[data-wire-model="' + model + '"]');
|
||||
|
||||
if (! select) {
|
||||
throw new Error('Helpdesk select ' + model + ' was not found.');
|
||||
}
|
||||
|
||||
select.value = value;
|
||||
select.dispatchEvent(new Event('change', { bubbles: true, composed: true }));
|
||||
|
||||
if (window.jQuery) {
|
||||
window.jQuery(select).val(value).trigger('change.select2').trigger('change.helpdesk-select2').trigger('change');
|
||||
}
|
||||
|
||||
let componentRoot = select;
|
||||
while (componentRoot && ! componentRoot.hasAttribute('wire:id')) {
|
||||
componentRoot = componentRoot.parentElement;
|
||||
}
|
||||
|
||||
if (componentRoot && window.Livewire) {
|
||||
const component = window.Livewire.find(componentRoot.getAttribute('wire:id'));
|
||||
|
||||
if (component) {
|
||||
component.set(model, value === '' ? null : value);
|
||||
}
|
||||
}
|
||||
JS);
|
||||
|
||||
$browser->pause(250);
|
||||
}
|
||||
|
||||
private function submitTicketCreateComponent(Browser $browser): void
|
||||
{
|
||||
$browser->waitUsing(10, 100, static function () use ($browser): bool {
|
||||
return (bool) $browser->script(<<<'JS'
|
||||
const button = document.querySelector('[dusk="ticket-create-submit"], [data-dusk="ticket-create-submit"]');
|
||||
return Boolean(button) && button.disabled === false;
|
||||
JS)[0];
|
||||
}, 'The helpdesk ticket create form was not ready to submit.');
|
||||
|
||||
$browser->script(<<<'JS'
|
||||
const root = document.querySelector('[dusk="helpdesk-ticket-create"], [data-dusk="helpdesk-ticket-create"]');
|
||||
|
||||
if (! root) {
|
||||
throw new Error('Helpdesk ticket create root was not found.');
|
||||
}
|
||||
|
||||
let componentRoot = root;
|
||||
while (componentRoot && ! componentRoot.hasAttribute('wire:id')) {
|
||||
componentRoot = componentRoot.parentElement;
|
||||
}
|
||||
|
||||
if (! componentRoot || ! window.Livewire) {
|
||||
throw new Error('Helpdesk Livewire create component was not found.');
|
||||
}
|
||||
|
||||
const component = window.Livewire.find(componentRoot.getAttribute('wire:id'));
|
||||
|
||||
if (! component) {
|
||||
throw new Error('Helpdesk Livewire create component instance was not found.');
|
||||
}
|
||||
|
||||
component.call('save');
|
||||
JS);
|
||||
}
|
||||
|
||||
private function waitForHelpdeskUploadToFinish(Browser $browser): void
|
||||
{
|
||||
$browser->waitUsing(30, 250, static function () use ($browser): bool {
|
||||
return (bool) $browser->script(<<<'JS'
|
||||
const input = document.querySelector('#attachments');
|
||||
const button = document.querySelector('[dusk="ticket-create-submit"], [data-dusk="ticket-create-submit"]');
|
||||
const selected = document.querySelector('[dusk="ticket-create-selected-attachments"], [data-dusk="ticket-create-selected-attachments"]');
|
||||
|
||||
return Boolean(input)
|
||||
&& input.files.length > 0
|
||||
&& Boolean(selected)
|
||||
&& Boolean(button)
|
||||
&& button.disabled === false;
|
||||
JS)[0];
|
||||
}, 'The Livewire temporary attachment upload did not finish before submit.');
|
||||
}
|
||||
|
||||
private function waitForTicketCreated(Browser $browser, string $subject): void
|
||||
{
|
||||
$browser->waitUsing(20, 250, static fn (): bool => Ticket::query()
|
||||
->where('subject', $subject)
|
||||
->exists(), 'The browser did not create the helpdesk ticket before the timeout.');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Modules\Helpdesk\Tests\Browser;
|
||||
|
||||
use Laravel\Dusk\Browser;
|
||||
use Modules\Helpdesk\Tests\Concerns\BuildsHelpdeskFixtures;
|
||||
use Tests\DuskTestCase;
|
||||
|
||||
/**
|
||||
* Browser regression coverage for the Helpdesk Create Ticket editor and footer actions.
|
||||
*
|
||||
* This test protects the previously-fixed regression where the create-ticket editor
|
||||
* lost its toolbar, native browser spellcheck surface, and compact Ticket controls
|
||||
* action-button styling.
|
||||
*/
|
||||
final class HelpdeskCreateTicketEditorBrowserTest extends DuskTestCase
|
||||
{
|
||||
use BuildsHelpdeskFixtures;
|
||||
|
||||
/**
|
||||
* Ensure the create-ticket editor keeps toolbar controls, contenteditable spellcheck,
|
||||
* and compact footer buttons aligned with the Ticket controls styling baseline.
|
||||
*/
|
||||
public function test_create_ticket_editor_toolbar_spellcheck_and_action_buttons_are_rendered_correctly(): void
|
||||
{
|
||||
$this->seedLookupData();
|
||||
|
||||
$user = $this->user([
|
||||
'name' => 'Create Ticket Editor Regression User',
|
||||
]);
|
||||
|
||||
$this->browse(function (Browser $browser) use ($user): void {
|
||||
$browser->loginAs($user)
|
||||
->visitRoute('helpdesk.tickets.create')
|
||||
->waitFor('@helpdesk-ticket-create', 10)
|
||||
->assertSee('Create ticket')
|
||||
->assertVisible('@ticket-create-submit')
|
||||
->assertVisible('@ticket-create-clear');
|
||||
|
||||
/** @var array{exists: bool, visible: bool, buttonCount: int, commands: array<int, string|null>} $toolbar */
|
||||
$toolbar = $browser->script(<<<'JS'
|
||||
const toolbar = document.querySelector('[data-helpdesk-editor-toolbar]');
|
||||
const buttons = toolbar ? Array.from(toolbar.querySelectorAll('button[data-helpdesk-editor-command]')) : [];
|
||||
|
||||
return {
|
||||
exists: Boolean(toolbar),
|
||||
visible: Boolean(toolbar && toolbar.offsetParent !== null),
|
||||
buttonCount: buttons.length,
|
||||
commands: buttons.map((button) => button.getAttribute('data-helpdesk-editor-command')),
|
||||
};
|
||||
JS)[0];
|
||||
|
||||
$this->assertTrue($toolbar['exists'], 'The create-ticket editor toolbar was not rendered.');
|
||||
$this->assertTrue($toolbar['visible'], 'The create-ticket editor toolbar was rendered but is not visible.');
|
||||
$this->assertGreaterThanOrEqual(4, $toolbar['buttonCount'], 'The create-ticket editor toolbar should expose formatting buttons.');
|
||||
$this->assertContains('bold', $toolbar['commands'], 'The create-ticket editor toolbar is missing the bold button.');
|
||||
$this->assertContains('italic', $toolbar['commands'], 'The create-ticket editor toolbar is missing the italic button.');
|
||||
|
||||
/** @var array{exists: bool, isContentEditable: bool, contenteditable: string|null, spellcheck: string|null, lang: string|null} $editor */
|
||||
$editor = $browser->script(<<<'JS'
|
||||
const root = document.querySelector('[dusk="ticket-body-editor"], [data-dusk="ticket-body-editor"]');
|
||||
const editor = root && root.matches('[contenteditable="true"]')
|
||||
? root
|
||||
: root?.querySelector('[contenteditable="true"]');
|
||||
|
||||
return {
|
||||
exists: Boolean(editor),
|
||||
isContentEditable: Boolean(editor?.isContentEditable),
|
||||
contenteditable: editor?.getAttribute('contenteditable') || null,
|
||||
spellcheck: editor?.getAttribute('spellcheck') || null,
|
||||
lang: editor?.getAttribute('lang') || null,
|
||||
};
|
||||
JS)[0];
|
||||
|
||||
$this->assertTrue($editor['exists'], 'The create-ticket editor contenteditable surface was not rendered.');
|
||||
$this->assertTrue($editor['isContentEditable'], 'The create-ticket editor must use a native contenteditable surface.');
|
||||
$this->assertSame('true', $editor['contenteditable'], 'The create-ticket editor must keep contenteditable="true".');
|
||||
$this->assertSame('true', $editor['spellcheck'], 'The create-ticket editor must keep browser spellcheck enabled.');
|
||||
$this->assertSame('en-GB', $editor['lang'], 'The create-ticket editor must use British English spellcheck language.');
|
||||
|
||||
/** @var array{create: array<int, string>, clear: array<int, string>, cancel: array<int, string>} $classes */
|
||||
$classes = $browser->script(<<<'JS'
|
||||
const classList = (selector) => Array.from(document.querySelector(selector)?.classList || []);
|
||||
|
||||
return {
|
||||
create: classList('[dusk="ticket-create-submit"], [data-dusk="ticket-create-submit"]'),
|
||||
clear: classList('[dusk="ticket-create-clear"], [data-dusk="ticket-create-clear"]'),
|
||||
cancel: classList('[dusk="ticket-create-cancel"], [data-dusk="ticket-create-cancel"]'),
|
||||
};
|
||||
JS)[0];
|
||||
|
||||
foreach (['inline-flex', 'items-center', 'justify-center', 'rounded-lg', 'px-4', 'py-2', 'text-xs', 'font-black', 'shadow-sm'] as $requiredClass) {
|
||||
$this->assertContains($requiredClass, $classes['create'], "The Create ticket button is missing the {$requiredClass} Ticket controls class.");
|
||||
}
|
||||
|
||||
foreach (['inline-flex', 'items-center', 'justify-center', 'rounded-lg', 'px-4', 'py-2', 'text-xs', 'font-black', 'shadow-sm'] as $requiredClass) {
|
||||
$this->assertContains($requiredClass, $classes['clear'], "The Clear button is missing the {$requiredClass} Ticket controls class.");
|
||||
}
|
||||
|
||||
foreach (['inline-flex', 'items-center', 'justify-center', 'rounded-lg', 'px-4', 'py-2', 'text-xs', 'font-black', 'shadow-sm'] as $requiredClass) {
|
||||
$this->assertContains($requiredClass, $classes['cancel'], "The Cancel button is missing the {$requiredClass} Ticket controls class.");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Modules\Helpdesk\Tests\Browser;
|
||||
|
||||
use Illuminate\Foundation\Testing\DatabaseMigrations;
|
||||
use Laravel\Dusk\Browser;
|
||||
use Modules\Helpdesk\Tests\Concerns\BuildsHelpdeskFixtures;
|
||||
use Tests\DuskTestCase;
|
||||
|
||||
final class HelpdeskKanbanBrowserTest extends DuskTestCase
|
||||
{
|
||||
use BuildsHelpdeskFixtures;
|
||||
use DatabaseMigrations;
|
||||
|
||||
/** @test */
|
||||
public function kanban_board_is_available_from_helpdesk_module(): void
|
||||
{
|
||||
$lookups = $this->seedLookupData();
|
||||
$user = $this->admin();
|
||||
|
||||
$this->ticket([
|
||||
'owner' => $user,
|
||||
'status_id' => $lookups['open']->id,
|
||||
'priority_id' => $lookups['high']->id,
|
||||
'subject' => 'Kanban browser ticket',
|
||||
]);
|
||||
|
||||
$this->browse(function (Browser $browser) use ($user): void {
|
||||
$browser->loginAs($user)
|
||||
->visitRoute('helpdesk.kanban')
|
||||
->assertPresent('@helpdesk-kanban-board')
|
||||
->assertSee('Kanban board')
|
||||
->assertSee('Kanban browser ticket')
|
||||
->assertPresent('@kanban-column-open');
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Modules\Helpdesk\Tests\Browser;
|
||||
|
||||
use Laravel\Dusk\Browser;
|
||||
use Modules\Helpdesk\Tests\Concerns\BuildsHelpdeskFixtures;
|
||||
use Tests\DuskTestCase;
|
||||
|
||||
final class HelpdeskNavigationBrowserTest extends DuskTestCase
|
||||
{
|
||||
use BuildsHelpdeskFixtures;
|
||||
|
||||
public function test_helpdesk_dashboard_and_main_pages_render_with_professional_headers(): void
|
||||
{
|
||||
$this->seedLookupData();
|
||||
$user = $this->admin(['name' => 'Mark Gregory']);
|
||||
|
||||
$this->browse(function (Browser $browser) use ($user): void {
|
||||
$browser->loginAs($user)
|
||||
->visitRoute('helpdesk.index')
|
||||
->waitFor('@helpdesk-dashboard-widget')
|
||||
->assertSee('Helpdesk dashboard')
|
||||
->assertSee('Total tickets')
|
||||
->assertSee('Open tickets')
|
||||
->assertSee('Closed tickets')
|
||||
->visitRoute('helpdesk.tickets.index')
|
||||
->waitFor('@helpdesk-ticket-index')
|
||||
->assertSee('Active tickets')
|
||||
->visitRoute('helpdesk.tickets.create')
|
||||
->waitFor('.helpdesk-ticket-form')
|
||||
->assertSee('Create ticket')
|
||||
->visitRoute('helpdesk.settings')
|
||||
->waitFor('@helpdesk-settings-manager')
|
||||
->assertSee('Support configuration')
|
||||
->assertSourceHas('Statuses')
|
||||
->assertSourceHas('Priorities')
|
||||
->assertSourceHas('Projects')
|
||||
->assertSourceHas('Workflow');
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Modules\Helpdesk\Tests\Browser;
|
||||
|
||||
use Laravel\Dusk\Browser;
|
||||
use Modules\Helpdesk\Tests\Concerns\BuildsHelpdeskFixtures;
|
||||
use Tests\DuskTestCase;
|
||||
|
||||
final class HelpdeskSettingsBrowserTest extends DuskTestCase
|
||||
{
|
||||
use BuildsHelpdeskFixtures;
|
||||
|
||||
public function test_settings_tabs_expose_lookup_git_and_workflow_setup_areas(): void
|
||||
{
|
||||
$this->seedLookupData();
|
||||
$admin = $this->admin(['name' => 'Helpdesk Settings Admin']);
|
||||
|
||||
$this->browse(function (Browser $browser) use ($admin): void {
|
||||
$browser->loginAs($admin)
|
||||
->visitRoute('helpdesk.settings')
|
||||
->waitFor('@helpdesk-settings-manager')
|
||||
->assertSee('Support configuration')
|
||||
->assertSourceMissing("route('helpdesk.tickets.index')")
|
||||
->assertSourceMissing("route('helpdesk.tickets.create')")
|
||||
->assertSourceMissing("route('helpdesk.kanban')")
|
||||
->assertDontSee('+ New Ticket')
|
||||
->assertDontSee('Kanban');
|
||||
|
||||
$this->openSettingsSection($browser, 'statuses', 'Statuses');
|
||||
$this->openSettingsSection($browser, 'priorities', 'Priorities');
|
||||
$this->openSettingsSection($browser, 'categories', 'Categories');
|
||||
$this->openSettingsSection($browser, 'teams', 'Teams');
|
||||
$this->openSettingsSection($browser, 'projects', 'Projects');
|
||||
$this->openSettingsSection($browser, 'git', 'Remote URL');
|
||||
$this->openSettingsSection($browser, 'workflow', 'HTML email body');
|
||||
});
|
||||
}
|
||||
|
||||
private function openSettingsSection(Browser $browser, string $section, string $expectedHeading): Browser
|
||||
{
|
||||
return $browser
|
||||
->click('@helpdesk-settings-section-' . $section)
|
||||
->waitForText($expectedHeading, 10);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,193 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Modules\Helpdesk\Tests\Browser;
|
||||
|
||||
use Laravel\Dusk\Browser;
|
||||
use Modules\Helpdesk\Models\Comment;
|
||||
use Modules\Helpdesk\Models\Ticket;
|
||||
use Modules\Helpdesk\Tests\Concerns\BuildsHelpdeskFixtures;
|
||||
use Tests\DuskTestCase;
|
||||
|
||||
final class HelpdeskTicketBrowserTest extends DuskTestCase
|
||||
{
|
||||
use BuildsHelpdeskFixtures;
|
||||
|
||||
public function test_user_can_create_and_view_ticket_in_browser(): void
|
||||
{
|
||||
$fixtures = $this->seedLookupData();
|
||||
$user = $this->user(['name' => 'Ticket Browser User']);
|
||||
|
||||
$subject = 'Browser created helpdesk ticket ' . uniqid();
|
||||
|
||||
$this->browse(function (Browser $browser) use ($user, $fixtures, $subject): void {
|
||||
$browser->loginAs($user)
|
||||
->visitRoute('helpdesk.tickets.create')
|
||||
->waitFor('@helpdesk-ticket-create', 10)
|
||||
->assertSee('Create ticket')
|
||||
->type('@ticket-subject', $subject);
|
||||
|
||||
$this->setHelpdeskEditor(
|
||||
$browser,
|
||||
'ticket-body-editor',
|
||||
'This browser ticket proves the form saves correctly.',
|
||||
);
|
||||
|
||||
$this->setHelpdeskSelect($browser, 'board_id', $fixtures['board']->id);
|
||||
$this->setHelpdeskSelect($browser, 'priority_id', $fixtures['high']->id);
|
||||
$this->setHelpdeskSelect($browser, 'category_id', $fixtures['category']->id);
|
||||
$this->setHelpdeskSelect($browser, 'team_id', $fixtures['team']->id);
|
||||
$this->setHelpdeskSelect($browser, 'project_id', $fixtures['project']->id);
|
||||
$browser->waitFor('@ticket-create-submit', 10)
|
||||
->press('@ticket-create-submit')
|
||||
->waitUntilMissing('@helpdesk-ticket-create', 15)
|
||||
->assertPathIs('/helpdesk/tickets')
|
||||
->assertSee($subject);
|
||||
|
||||
$ticket = Ticket::query()
|
||||
->where('subject', $subject)
|
||||
->latest('id')
|
||||
->firstOrFail();
|
||||
|
||||
$browser->visitRoute('helpdesk.tickets.show', $ticket)
|
||||
->waitFor('@helpdesk-ticket-show', 15)
|
||||
->assertSee($subject)
|
||||
->assertSee('This browser ticket proves the form saves correctly.');
|
||||
});
|
||||
}
|
||||
|
||||
public function test_user_can_add_nested_response_in_browser(): void
|
||||
{
|
||||
$fixtures = $this->seedLookupData();
|
||||
$user = $this->user(['name' => 'Response Browser User']);
|
||||
|
||||
$ticket = $this->ticket([
|
||||
'owner' => $user,
|
||||
'status_id' => $fixtures['open']->id,
|
||||
'board_id' => $fixtures['board']->id,
|
||||
'priority_id' => $fixtures['normal']->id,
|
||||
'category_id' => $fixtures['category']->id,
|
||||
'team_id' => $fixtures['team']->id,
|
||||
'project_id' => $fixtures['project']->id,
|
||||
'subject' => 'Browser response ticket',
|
||||
]);
|
||||
|
||||
$parent = Comment::query()->create([
|
||||
'ticket_id' => $ticket->id,
|
||||
'user_id' => $user->id,
|
||||
'parent_id' => null,
|
||||
'title' => 'Browser canned response',
|
||||
'body' => '<p>Hello Response Browser User</p><p>Inserted canned browser response for ' . $ticket->reference . '</p>',
|
||||
'is_internal' => false,
|
||||
]);
|
||||
|
||||
Comment::query()->create([
|
||||
'ticket_id' => $ticket->id,
|
||||
'user_id' => $user->id,
|
||||
'parent_id' => $parent->id,
|
||||
'title' => 'RE: Browser canned response',
|
||||
'body' => 'Nested browser response',
|
||||
'is_internal' => false,
|
||||
]);
|
||||
|
||||
$this->browse(function (Browser $browser) use ($user, $ticket): void {
|
||||
$browser->loginAs($user)
|
||||
->visitRoute('helpdesk.tickets.show', ['ticket' => $ticket->getKey()])
|
||||
->waitForText('Browser response ticket', 10)
|
||||
->assertSee('Browser response ticket')
|
||||
->assertDontSee('UNID')
|
||||
->assertDontSee('GUID');
|
||||
});
|
||||
|
||||
$this->assertDatabaseHas('helpdesk_comments', [
|
||||
'ticket_id' => $ticket->id,
|
||||
'parent_id' => null,
|
||||
'body' => '<p>Hello Response Browser User</p><p>Inserted canned browser response for ' . $ticket->reference . '</p>',
|
||||
]);
|
||||
|
||||
$this->assertDatabaseHas('helpdesk_comments', [
|
||||
'ticket_id' => $ticket->id,
|
||||
'parent_id' => $parent->id,
|
||||
'body' => 'Nested browser response',
|
||||
]);
|
||||
}
|
||||
|
||||
private function setHelpdeskEditor(Browser $browser, string $dusk, string $html): void
|
||||
{
|
||||
$encodedDusk = json_encode($dusk, JSON_THROW_ON_ERROR);
|
||||
$encodedHtml = json_encode($html, JSON_THROW_ON_ERROR);
|
||||
|
||||
$browser->script(<<<JS
|
||||
const key = {$encodedDusk};
|
||||
const html = {$encodedHtml};
|
||||
const root = document.querySelector('[dusk="' + key + '"]') || document.querySelector('[data-dusk="' + key + '"]');
|
||||
|
||||
if (! root) {
|
||||
throw new Error('Helpdesk editor ' + key + ' was not found.');
|
||||
}
|
||||
|
||||
const editor = root.matches('textarea, input, [contenteditable="true"]')
|
||||
? root
|
||||
: root.querySelector('textarea, input, [contenteditable="true"]');
|
||||
|
||||
if (! editor) {
|
||||
throw new Error('Helpdesk editor content area ' + key + ' was not found.');
|
||||
}
|
||||
|
||||
editor.focus();
|
||||
|
||||
if (editor.matches('textarea, input')) {
|
||||
editor.value = html.replace(/^<p>/, '').replace(/<\/p>$/, '');
|
||||
} else {
|
||||
editor.innerHTML = html;
|
||||
}
|
||||
|
||||
editor.dispatchEvent(new InputEvent('input', {
|
||||
bubbles: true,
|
||||
composed: true,
|
||||
inputType: 'insertText',
|
||||
data: editor.value || editor.textContent || ' ',
|
||||
}));
|
||||
|
||||
editor.dispatchEvent(new Event('change', { bubbles: true, composed: true }));
|
||||
editor.dispatchEvent(new KeyboardEvent('keyup', { bubbles: true, composed: true }));
|
||||
editor.dispatchEvent(new Event('blur', { bubbles: true, composed: true }));
|
||||
|
||||
const model = root.getAttribute('wire:model')
|
||||
|| root.getAttribute('wire:model.live')
|
||||
|| root.getAttribute('wire:model.defer')
|
||||
|| root.getAttribute('data-wire-model');
|
||||
|
||||
const componentRoot = root.closest('[wire\\\\:id]');
|
||||
|
||||
if (model && componentRoot && window.Livewire) {
|
||||
const component = window.Livewire.find(componentRoot.getAttribute('wire:id'));
|
||||
|
||||
if (component) {
|
||||
component.set(model, html);
|
||||
}
|
||||
}
|
||||
JS);
|
||||
}
|
||||
|
||||
private function setHelpdeskSelect(Browser $browser, string $model, int|string|null $value): void
|
||||
{
|
||||
$encodedModel = json_encode($model, JSON_THROW_ON_ERROR);
|
||||
$encodedValue = json_encode($value === null ? '' : (string) $value, JSON_THROW_ON_ERROR);
|
||||
|
||||
$browser->script(<<<JS
|
||||
const model = {$encodedModel};
|
||||
const value = {$encodedValue};
|
||||
const select = document.querySelector('[data-wire-model="' + model + '"]');
|
||||
if (! select) {
|
||||
throw new Error('Helpdesk select '+model+' was not found.');
|
||||
}
|
||||
select.value = value;
|
||||
select.dispatchEvent(new Event('change', { bubbles: true }));
|
||||
if (window.jQuery) {
|
||||
window.jQuery(select).val(value).trigger('change.select2').trigger('change.helpdesk-select2').trigger('change');
|
||||
}
|
||||
JS);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user