Repo rebuild

This commit is contained in:
Mark Gregory
2026-09-15 12:33:28 +01:00
commit be28c4feec
5629 changed files with 317074 additions and 0 deletions
@@ -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);
}
}