Merge branch 'issue/Company_Dusk_Failure'
# Conflicts: # Modules/Helpdesk/changelog.yaml
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.');
|
||||
}
|
||||
}
|
||||
@@ -48,6 +48,44 @@ final class KanbanBoardTest extends TestCase
|
||||
->assertSee('High');
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function test_ticket_can_be_moved_to_another_board(): void
|
||||
{
|
||||
$lookups = $this->seedLookupData();
|
||||
$user = $this->admin();
|
||||
|
||||
$boardA = $lookups['board'];
|
||||
|
||||
$boardB = KanbanBoardModel::query()->create([
|
||||
'name' => 'Regression Target Board',
|
||||
'slug' => 'regression-target-board',
|
||||
'is_default' => false,
|
||||
'sort_order' => 50,
|
||||
]);
|
||||
|
||||
$boardB->statuses()->sync([
|
||||
$lookups['open']->id => ['sort_order' => 10],
|
||||
]);
|
||||
|
||||
$ticket = $this->ticket([
|
||||
'owner' => $user,
|
||||
'status_id' => $lookups['open']->id,
|
||||
'priority_id' => $lookups['normal']->id,
|
||||
'board_id' => $boardA->id,
|
||||
]);
|
||||
|
||||
$this->actingAs($user);
|
||||
|
||||
Livewire::test(KanbanBoard::class)
|
||||
->call('moveTicketToBoard', $ticket->id, $boardB->id)
|
||||
->assertHasNoErrors();
|
||||
|
||||
$ticket->refresh();
|
||||
|
||||
$this->assertSame($boardB->id, $ticket->board_id);
|
||||
$this->assertSame($lookups['open']->id, $ticket->status_id);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function kanban_board_can_move_ticket_to_closed_status(): void
|
||||
{
|
||||
|
||||
@@ -29,6 +29,12 @@ final class TicketBranchCommandUiFilesTest extends TestCase
|
||||
self::assertStringContainsString('data-helpdesk-branch-command', $view);
|
||||
self::assertStringContainsString('git checkout -b bugfix/{{ $ticket->reference }}-', $view);
|
||||
self::assertStringContainsString('data-helpdesk-branch-suffix', $view);
|
||||
self::assertStringContainsString('data-helpdesk-branch-prefix-select', $view);
|
||||
self::assertStringContainsString('normaliseHelpdeskBranchPrefix', $view);
|
||||
self::assertStringContainsString('feature/', $view);
|
||||
self::assertStringContainsString('bugfix/', $view);
|
||||
self::assertStringContainsString('hotfix/', $view);
|
||||
self::assertStringContainsString('support/', $view);
|
||||
self::assertStringContainsString('data-helpdesk-copy-branch-command', $view);
|
||||
self::assertStringContainsString('copyHelpdeskBranchCommand', $view);
|
||||
self::assertStringContainsString('normaliseHelpdeskBranchSuffix', $view);
|
||||
|
||||
@@ -4,7 +4,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace Modules\Helpdesk\Tests\Feature;
|
||||
|
||||
use App\Models\Company;
|
||||
use Modules\Company\Models\Company;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Http\UploadedFile;
|
||||
use Illuminate\Support\Facades\Mail;
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Modules\Helpdesk\Tests\Feature;
|
||||
|
||||
use Tests\TestCase;
|
||||
|
||||
final class TicketCreateAttachmentBrowserSafetyTest extends TestCase
|
||||
{
|
||||
public function test_ticket_create_uses_standard_livewire_temporary_uploads(): void
|
||||
{
|
||||
$view = file_get_contents(dirname(__DIR__, 2) . '/Resources/views/livewire/ticket-create.blade.php');
|
||||
|
||||
self::assertIsString($view);
|
||||
self::assertStringContainsString('wire:submit.prevent="save"', $view);
|
||||
self::assertStringContainsString('type="file"', $view);
|
||||
self::assertStringContainsString('wire:model="attachments"', $view);
|
||||
self::assertStringContainsString('multiple', $view);
|
||||
self::assertStringContainsString('wire:key="ticket-create-attachments-input"', $view);
|
||||
}
|
||||
|
||||
public function test_ticket_create_does_not_mix_existing_ticket_upload_behaviour_into_create_form(): void
|
||||
{
|
||||
$view = file_get_contents(dirname(__DIR__, 2) . '/Resources/views/livewire/ticket-create.blade.php');
|
||||
|
||||
self::assertIsString($view);
|
||||
self::assertStringNotContainsString('@vite([\'resources/css/app.css\', \'resources/js/app.js\'])', $view);
|
||||
self::assertStringNotContainsString('helpdeskAttachmentDropzone', $view);
|
||||
self::assertStringNotContainsString('DataTransfer', $view);
|
||||
self::assertStringNotContainsString('$wire.uploadMultiple', $view);
|
||||
self::assertStringNotContainsString('input.files =', $view);
|
||||
self::assertStringNotContainsString('helpdesk-editor-files-pasted', $view);
|
||||
}
|
||||
|
||||
public function test_ticket_create_shows_uploaded_temporary_file_names_from_livewire_property(): void
|
||||
{
|
||||
$view = file_get_contents(dirname(__DIR__, 2) . '/Resources/views/livewire/ticket-create.blade.php');
|
||||
|
||||
self::assertIsString($view);
|
||||
self::assertStringContainsString('@if (count($attachments) > 0)', $view);
|
||||
self::assertStringContainsString('$attachment->getClientOriginalName()', $view);
|
||||
self::assertStringContainsString('wire:loading wire:target="attachments"', $view);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Modules\Helpdesk\Tests\Feature;
|
||||
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Http\UploadedFile;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Livewire\Livewire;
|
||||
use Modules\Helpdesk\Livewire\TicketCreate;
|
||||
use Modules\Helpdesk\Models\Attachment;
|
||||
use Modules\Helpdesk\Models\Ticket;
|
||||
use Modules\Helpdesk\Tests\Concerns\BuildsHelpdeskFixtures;
|
||||
use Tests\TestCase;
|
||||
|
||||
final class TicketCreateAttachmentPipelineTest extends TestCase
|
||||
{
|
||||
use BuildsHelpdeskFixtures;
|
||||
use RefreshDatabase;
|
||||
|
||||
public function test_ticket_create_persists_a_single_attachment_through_livewire(): void
|
||||
{
|
||||
Storage::fake('local');
|
||||
config()->set('helpdesk.disk', 'local');
|
||||
|
||||
$fixtures = $this->seedLookupData();
|
||||
$user = $this->user(['name' => 'Attachment Create User']);
|
||||
$file = UploadedFile::fake()->create('warehouse-error.log', 12, 'text/plain');
|
||||
|
||||
Livewire::actingAs($user)
|
||||
->test(TicketCreate::class)
|
||||
->set('subject', 'Create ticket with one attachment')
|
||||
->set('body', '<p>This ticket must persist a Livewire upload attachment.</p>')
|
||||
->set('board_id', $fixtures['board']->id)
|
||||
->set('priority_id', $fixtures['high']->id)
|
||||
->set('category_id', $fixtures['category']->id)
|
||||
->set('team_id', $fixtures['team']->id)
|
||||
->set('project_id', $fixtures['project']->id)
|
||||
->set('attachments', [$file])
|
||||
->call('save')
|
||||
->assertHasNoErrors()
|
||||
->assertRedirect(route('helpdesk.tickets.index'));
|
||||
|
||||
$ticket = Ticket::query()
|
||||
->where('subject', 'Create ticket with one attachment')
|
||||
->firstOrFail();
|
||||
|
||||
$attachment = Attachment::query()
|
||||
->where('ticket_id', $ticket->id)
|
||||
->whereNull('comment_id')
|
||||
->firstOrFail();
|
||||
|
||||
self::assertSame($user->id, (int) $attachment->user_id);
|
||||
self::assertSame('local', $attachment->disk);
|
||||
self::assertSame('warehouse-error.log', $attachment->original_name);
|
||||
self::assertSame('log', $attachment->extension());
|
||||
Storage::disk('local')->assertExists($attachment->path);
|
||||
}
|
||||
|
||||
public function test_ticket_create_persists_multiple_attachments_through_livewire(): void
|
||||
{
|
||||
Storage::fake('local');
|
||||
config()->set('helpdesk.disk', 'local');
|
||||
|
||||
$fixtures = $this->seedLookupData();
|
||||
$user = $this->user(['name' => 'Multiple Attachment User']);
|
||||
|
||||
Livewire::actingAs($user)
|
||||
->test(TicketCreate::class)
|
||||
->set('subject', 'Create ticket with multiple attachments')
|
||||
->set('body', '<p>This ticket must persist every selected Livewire upload.</p>')
|
||||
->set('board_id', $fixtures['board']->id)
|
||||
->set('priority_id', $fixtures['normal']->id)
|
||||
->set('category_id', $fixtures['category']->id)
|
||||
->set('team_id', $fixtures['team']->id)
|
||||
->set('project_id', $fixtures['project']->id)
|
||||
->set('attachments', [
|
||||
UploadedFile::fake()->create('first-proof.pdf', 20, 'application/pdf'),
|
||||
UploadedFile::fake()->create('second-proof.txt', 8, 'text/plain'),
|
||||
UploadedFile::fake()->create('third-proof.csv', 6, 'text/csv'),
|
||||
])
|
||||
->call('save')
|
||||
->assertHasNoErrors()
|
||||
->assertRedirect(route('helpdesk.tickets.index'));
|
||||
|
||||
$ticket = Ticket::query()
|
||||
->where('subject', 'Create ticket with multiple attachments')
|
||||
->firstOrFail();
|
||||
|
||||
self::assertSame(3, Attachment::query()->where('ticket_id', $ticket->id)->whereNull('comment_id')->count());
|
||||
|
||||
foreach (['first-proof.pdf', 'second-proof.txt', 'third-proof.csv'] as $name) {
|
||||
$attachment = Attachment::query()
|
||||
->where('ticket_id', $ticket->id)
|
||||
->where('original_name', $name)
|
||||
->firstOrFail();
|
||||
|
||||
Storage::disk('local')->assertExists($attachment->path);
|
||||
}
|
||||
}
|
||||
|
||||
public function test_ticket_create_accepts_source_code_attachments_through_livewire(): void
|
||||
{
|
||||
Storage::fake('local');
|
||||
config()->set('helpdesk.disk', 'local');
|
||||
|
||||
$fixtures = $this->seedLookupData();
|
||||
$user = $this->user(['name' => 'Source Attachment User']);
|
||||
|
||||
Livewire::actingAs($user)
|
||||
->test(TicketCreate::class)
|
||||
->set('subject', 'Create ticket with source attachments')
|
||||
->set('body', '<p>This ticket must accept source files by extension.</p>')
|
||||
->set('board_id', $fixtures['board']->id)
|
||||
->set('priority_id', $fixtures['normal']->id)
|
||||
->set('category_id', $fixtures['category']->id)
|
||||
->set('team_id', $fixtures['team']->id)
|
||||
->set('project_id', $fixtures['project']->id)
|
||||
->set('attachments', [
|
||||
UploadedFile::fake()->create('TicketCreate.php', 10, 'text/plain'),
|
||||
UploadedFile::fake()->create('upload-widget.js', 10, 'text/plain'),
|
||||
UploadedFile::fake()->create('warehouse-sync.rpgle', 10, 'text/plain'),
|
||||
])
|
||||
->call('save')
|
||||
->assertHasNoErrors()
|
||||
->assertRedirect(route('helpdesk.tickets.index'));
|
||||
|
||||
$ticket = Ticket::query()
|
||||
->where('subject', 'Create ticket with source attachments')
|
||||
->firstOrFail();
|
||||
|
||||
foreach (['TicketCreate.php', 'upload-widget.js', 'warehouse-sync.rpgle'] as $name) {
|
||||
$attachment = Attachment::query()
|
||||
->where('ticket_id', $ticket->id)
|
||||
->where('original_name', $name)
|
||||
->firstOrFail();
|
||||
|
||||
self::assertTrue($attachment->isSourceCode(), $name . ' should be categorised as source code.');
|
||||
Storage::disk('local')->assertExists($attachment->path);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* TicketDeveloperActionsUiFilesTest.php
|
||||
*
|
||||
* Guards the ticket-level developer actions workflow against UI regressions.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Modules\Helpdesk\Tests\Feature;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
final class TicketDeveloperActionsUiFilesTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* Ensure the Git activity card has been promoted to a complete developer actions panel.
|
||||
*/
|
||||
public function test_ticket_show_exposes_developer_actions_panel(): void
|
||||
{
|
||||
$viewPath = dirname(__DIR__, 2) . '/Resources/views/livewire/ticket-show.blade.php';
|
||||
$view = file_get_contents($viewPath);
|
||||
|
||||
self::assertIsString($view);
|
||||
self::assertStringContainsString('data-helpdesk-developer-actions', $view);
|
||||
self::assertStringContainsString('Developer actions', $view);
|
||||
self::assertStringContainsString('Open repo', $view);
|
||||
self::assertStringContainsString('Open branch', $view);
|
||||
self::assertStringContainsString('Pull requests', $view);
|
||||
self::assertStringContainsString('Activity status', $view);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure the panel keeps copyable GitFlow branch, commit and pull request templates.
|
||||
*/
|
||||
public function test_ticket_show_exposes_copyable_gitflow_templates(): void
|
||||
{
|
||||
$viewPath = dirname(__DIR__, 2) . '/Resources/views/livewire/ticket-show.blade.php';
|
||||
$view = file_get_contents($viewPath);
|
||||
|
||||
self::assertIsString($view);
|
||||
self::assertStringContainsString('Branch create command', $view);
|
||||
self::assertStringContainsString('\'git checkout -b bugfix/\' . $ticket->reference . \'-\'', $view);
|
||||
self::assertStringContainsString('data-helpdesk-branch-prefix-select', $view);
|
||||
self::assertStringContainsString('$gitflowPrefixes', $view);
|
||||
self::assertStringContainsString('normaliseHelpdeskBranchPrefix', $view);
|
||||
self::assertStringContainsString('Commit message', $view);
|
||||
self::assertStringContainsString('Pull request title', $view);
|
||||
self::assertStringContainsString('data-helpdesk-copy-template', $view);
|
||||
self::assertStringContainsString('data-helpdesk-copy-template-button', $view);
|
||||
self::assertStringContainsString('copyHelpdeskTemplate', $view);
|
||||
self::assertStringContainsString('copyTextToClipboard', $view);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure linked Git activity still renders branch and commit badges instead of being hidden by the new panel.
|
||||
*/
|
||||
public function test_ticket_show_still_renders_linked_git_activity_rows(): void
|
||||
{
|
||||
$viewPath = dirname(__DIR__, 2) . '/Resources/views/livewire/ticket-show.blade.php';
|
||||
$view = file_get_contents($viewPath);
|
||||
|
||||
self::assertIsString($view);
|
||||
self::assertStringContainsString('@forelse ($gitActivity as $commit)', $view);
|
||||
self::assertStringContainsString('$isBranchActivity = $commit->short_hash === \'branch\';', $view);
|
||||
self::assertStringContainsString('{{ $isBranchActivity ? \'Branch\' : \'Commit\' }}', $view);
|
||||
self::assertStringContainsString('No Git activity linked.', $view);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Modules\Helpdesk\Tests\Feature;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
final class TicketIndexDirectUserNPlusOneFilesTest extends TestCase
|
||||
{
|
||||
public function test_ticket_index_eager_loads_required_relations(): void
|
||||
{
|
||||
$source = file_get_contents(dirname(__DIR__, 2).'/Livewire/TicketIndex.php');
|
||||
|
||||
self::assertIsString($source);
|
||||
|
||||
self::assertStringContainsString("->with(['status', 'priority', 'category', 'project', 'team'])", $source);
|
||||
self::assertStringNotContainsString("'user:id,name,email'", $source);
|
||||
self::assertStringNotContainsString("'owner:id,name,email'", $source);
|
||||
self::assertStringNotContainsString("'agent:id,name,email'", $source);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Modules\Helpdesk\Tests\Feature;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
final class TicketIndexUserNPlusOneFilesTest extends TestCase
|
||||
{
|
||||
public function test_ticket_index_uses_scalar_agent_name(): void
|
||||
{
|
||||
$component = file_get_contents(dirname(__DIR__, 2).'/Livewire/TicketIndex.php');
|
||||
$view = file_get_contents(dirname(__DIR__, 2).'/Resources/views/livewire/ticket-index.blade.php');
|
||||
|
||||
self::assertIsString($component);
|
||||
self::assertIsString($view);
|
||||
|
||||
self::assertStringContainsString("->select('helpdesk_tickets.*')", $component);
|
||||
self::assertStringContainsString("'agent_name' => User::query()", $component);
|
||||
self::assertStringContainsString("->whereColumn('users.id', 'helpdesk_tickets.agent_id')", $component);
|
||||
|
||||
self::assertStringContainsString('$ticket->agent_name ?? \'Unassigned\'', $view);
|
||||
self::assertStringNotContainsString('$ticket->agent?->name ?? \'Unassigned\'', $view);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Modules\Helpdesk\Tests\Feature;
|
||||
|
||||
use Tests\TestCase;
|
||||
|
||||
final class TicketShowCommentRelationsFilesTest extends TestCase
|
||||
{
|
||||
public function test_ticket_show_only_eager_loads_real_comment_relations(): void
|
||||
{
|
||||
$source = file_get_contents(dirname(__DIR__, 2) . '/Livewire/TicketShow.php');
|
||||
|
||||
self::assertIsString($source);
|
||||
self::assertStringContainsString("->with(['user.company', 'attachments'])", $source);
|
||||
self::assertStringNotContainsString("->with(['user.company', 'owner:id,name,email,company_id', 'agent:id,name,email', 'attachments'])", $source);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Modules\Helpdesk\Tests\Feature;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
final class TicketUserNPlusOneFilesTest extends TestCase
|
||||
{
|
||||
public function test_ticket_queries_only_eager_load_required_relations(): void
|
||||
{
|
||||
$index = file_get_contents(dirname(__DIR__, 2).'/Livewire/TicketIndex.php');
|
||||
$kanban = file_get_contents(dirname(__DIR__, 2).'/Livewire/KanbanBoard.php');
|
||||
$dashboard = file_get_contents(dirname(__DIR__, 2).'/Livewire/HelpdeskDashboardWidget.php');
|
||||
|
||||
self::assertIsString($index);
|
||||
self::assertIsString($kanban);
|
||||
self::assertIsString($dashboard);
|
||||
|
||||
// Index deliberately avoids loading user relations.
|
||||
self::assertStringNotContainsString("'user:id,name,email'", $index);
|
||||
self::assertStringNotContainsString("'owner:id,name,email'", $index);
|
||||
self::assertStringNotContainsString("'agent:id,name,email'", $index);
|
||||
|
||||
self::assertStringContainsString("'owner:id,name,email'", $kanban);
|
||||
|
||||
self::assertStringContainsString("'agent:id,name,email'", $kanban);
|
||||
|
||||
// Dashboard intentionally only loads the relations it renders.
|
||||
|
||||
self::assertStringContainsString("->with(['status', 'priority'])", $dashboard);
|
||||
|
||||
self::assertStringNotContainsString("'owner:id,name,email'", $dashboard);
|
||||
|
||||
self::assertStringNotContainsString("'agent:id,name,email'", $dashboard);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user