Model ticket link counterparts explicitly
This commit is contained in:
@@ -20,9 +20,9 @@ class TicketLinkTest extends TestCase
|
||||
use RefreshDatabase;
|
||||
|
||||
/**
|
||||
* Ensure the ticket link factory normalizes the stored source and target ticket ids.
|
||||
* Ensure the ticket link factory preserves the provided source and target direction.
|
||||
*/
|
||||
public function test_ticket_link_factory_normalizes_ticket_order(): void
|
||||
public function test_ticket_link_factory_preserves_ticket_direction(): void
|
||||
{
|
||||
$project = Project::factory()->create([
|
||||
'key' => 'CORE',
|
||||
@@ -61,8 +61,8 @@ class TicketLinkTest extends TestCase
|
||||
'ticket_link_type_id' => $ticketLinkType->id,
|
||||
]);
|
||||
|
||||
$this->assertSame($firstTicket->id, $ticketLink->source_ticket_id);
|
||||
$this->assertSame($secondTicket->id, $ticketLink->target_ticket_id);
|
||||
$this->assertSame($secondTicket->id, $ticketLink->source_ticket_id);
|
||||
$this->assertSame($firstTicket->id, $ticketLink->target_ticket_id);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -22,6 +22,7 @@ class TicketLinkTypeTest extends TestCase
|
||||
|
||||
$this->assertNotNull($ticketLinkType->project_id);
|
||||
$this->assertIsString($ticketLinkType->label);
|
||||
$this->assertSame($ticketLinkType->label, $ticketLinkType->counterpart_label);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -39,12 +40,14 @@ class TicketLinkTypeTest extends TestCase
|
||||
$this->assertDatabaseHas('ticket_link_types', [
|
||||
'project_id' => $coreProject->id,
|
||||
'label' => 'Relates to',
|
||||
'counterpart_label' => 'Relates to',
|
||||
'position' => 1,
|
||||
]);
|
||||
|
||||
$this->assertDatabaseHas('ticket_link_types', [
|
||||
'project_id' => $coreProject->id,
|
||||
'label' => 'Blocks',
|
||||
'counterpart_label' => 'Blocked by',
|
||||
'position' => 2,
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -55,6 +55,7 @@ class ProjectTicketControllerTest extends TestCase
|
||||
]);
|
||||
$ticketLinkType = TicketLinkType::factory()->for($project)->create([
|
||||
'label' => 'Relates to',
|
||||
'counterpart_label' => 'Relates to',
|
||||
'position' => 1,
|
||||
]);
|
||||
|
||||
@@ -137,6 +138,7 @@ class ProjectTicketControllerTest extends TestCase
|
||||
->where('ticket.worklog_entries.0.actor.name', $viewer->name)
|
||||
->where('linkedTickets.0.linked_ticket.ticket_key', 'CORE-2')
|
||||
->where('linkedTickets.0.ticket_link_type.label', 'Relates to')
|
||||
->where('linkedTickets.0.ticket_link_type.counterpart_label', 'Relates to')
|
||||
->where('repositoryBranches', [])
|
||||
->where('repositoryPullRequests', [])
|
||||
->where('canEditTicket', false)
|
||||
@@ -144,6 +146,62 @@ class ProjectTicketControllerTest extends TestCase
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure linked tickets show the counterpart label from the other ticket perspective.
|
||||
*/
|
||||
public function test_ticket_detail_uses_counterpart_link_label_for_target_ticket(): void
|
||||
{
|
||||
$viewer = User::factory()->create();
|
||||
$project = Project::factory()->create([
|
||||
'key' => 'CORE',
|
||||
]);
|
||||
|
||||
app(InitializeProjectTicketMetadata::class)->handle($project);
|
||||
|
||||
$priority = $project->ticketPriorities()->firstOrFail();
|
||||
$taskType = $project->ticketTypes()
|
||||
->where('is_parent', false)
|
||||
->orderBy('position')
|
||||
->firstOrFail();
|
||||
$sourceTicket = Ticket::factory()->create([
|
||||
'project_id' => $project->id,
|
||||
'sequence_number' => 1,
|
||||
'ticket_key' => 'CORE-1',
|
||||
'ticket_priority_id' => $priority->id,
|
||||
'ticket_type_id' => $taskType->id,
|
||||
'title' => 'Source ticket',
|
||||
]);
|
||||
$targetTicket = Ticket::factory()->create([
|
||||
'project_id' => $project->id,
|
||||
'sequence_number' => 2,
|
||||
'ticket_key' => 'CORE-2',
|
||||
'ticket_priority_id' => $priority->id,
|
||||
'ticket_type_id' => $taskType->id,
|
||||
'title' => 'Target ticket',
|
||||
]);
|
||||
$ticketLinkType = TicketLinkType::factory()->for($project)->create([
|
||||
'label' => 'Blocks',
|
||||
'counterpart_label' => 'Blocked by',
|
||||
'position' => 1,
|
||||
]);
|
||||
|
||||
TicketLink::factory()->create([
|
||||
'source_ticket_id' => $sourceTicket->id,
|
||||
'target_ticket_id' => $targetTicket->id,
|
||||
'ticket_link_type_id' => $ticketLinkType->id,
|
||||
]);
|
||||
|
||||
$this->syncProjectPermissions($project, $viewer, ['view']);
|
||||
|
||||
$this->actingAs($viewer)
|
||||
->get(route('projects.tickets.show', [$project, $targetTicket]))
|
||||
->assertOk()
|
||||
->assertInertia(fn (Assert $page) => $page
|
||||
->where('linkedTickets.0.linked_ticket.ticket_key', 'CORE-1')
|
||||
->where('linkedTickets.0.ticket_link_type.label', 'Blocked by')
|
||||
->where('linkedTickets.0.ticket_link_type.counterpart_label', 'Blocked by'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure project admins can still inspect the author when project author visibility is disabled.
|
||||
*/
|
||||
|
||||
@@ -33,7 +33,8 @@ class ProjectTicketLinkControllerTest extends TestCase
|
||||
->orderBy('position')
|
||||
->firstOrFail();
|
||||
$ticketLinkType = TicketLinkType::factory()->for($project)->create([
|
||||
'label' => 'Relates to',
|
||||
'label' => 'Blocks',
|
||||
'counterpart_label' => 'Blocked by',
|
||||
'position' => 1,
|
||||
]);
|
||||
$ticket = Ticket::factory()->create([
|
||||
@@ -59,11 +60,11 @@ class ProjectTicketLinkControllerTest extends TestCase
|
||||
'ticket_link_type_id' => $ticketLinkType->id,
|
||||
])
|
||||
->assertRedirect(route('projects.tickets.show', [$project, $ticket]))
|
||||
->assertSessionHas('status', 'Relates to link was saved.');
|
||||
->assertSessionHas('status', 'Blocks link was saved.');
|
||||
|
||||
$this->assertDatabaseHas('ticket_links', [
|
||||
'source_ticket_id' => min($ticket->id, $linkedTicket->id),
|
||||
'target_ticket_id' => max($ticket->id, $linkedTicket->id),
|
||||
'source_ticket_id' => $ticket->id,
|
||||
'target_ticket_id' => $linkedTicket->id,
|
||||
'ticket_link_type_id' => $ticketLinkType->id,
|
||||
]);
|
||||
|
||||
@@ -71,7 +72,14 @@ class ProjectTicketLinkControllerTest extends TestCase
|
||||
'ticket_id' => $ticket->id,
|
||||
'actor_id' => $user->id,
|
||||
'entry_type' => 'link_updated',
|
||||
'summary' => 'Linked CORE-2 as Relates to.',
|
||||
'summary' => 'Linked CORE-2 as Blocks.',
|
||||
]);
|
||||
|
||||
$this->assertDatabaseHas('ticket_worklog_entries', [
|
||||
'ticket_id' => $linkedTicket->id,
|
||||
'actor_id' => $user->id,
|
||||
'entry_type' => 'link_updated',
|
||||
'summary' => 'Linked CORE-1 as Blocked by.',
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -99,6 +107,7 @@ class ProjectTicketLinkControllerTest extends TestCase
|
||||
->firstOrFail();
|
||||
$ticketLinkType = TicketLinkType::factory()->for($project)->create([
|
||||
'label' => 'Relates to',
|
||||
'counterpart_label' => 'Relates to',
|
||||
'position' => 1,
|
||||
]);
|
||||
$ticket = Ticket::factory()->create([
|
||||
@@ -155,7 +164,8 @@ class ProjectTicketLinkControllerTest extends TestCase
|
||||
->orderBy('position')
|
||||
->firstOrFail();
|
||||
$ticketLinkType = TicketLinkType::factory()->for($project)->create([
|
||||
'label' => 'Relates to',
|
||||
'label' => 'Blocks',
|
||||
'counterpart_label' => 'Blocked by',
|
||||
'position' => 1,
|
||||
]);
|
||||
$ticket = Ticket::factory()->create([
|
||||
@@ -173,8 +183,8 @@ class ProjectTicketLinkControllerTest extends TestCase
|
||||
'ticket_type_id' => $taskType->id,
|
||||
]);
|
||||
$ticketLink = TicketLink::factory()->create([
|
||||
'source_ticket_id' => min($ticket->id, $linkedTicket->id),
|
||||
'target_ticket_id' => max($ticket->id, $linkedTicket->id),
|
||||
'source_ticket_id' => $ticket->id,
|
||||
'target_ticket_id' => $linkedTicket->id,
|
||||
'ticket_link_type_id' => $ticketLinkType->id,
|
||||
]);
|
||||
|
||||
@@ -183,7 +193,7 @@ class ProjectTicketLinkControllerTest extends TestCase
|
||||
$this->actingAs($user)
|
||||
->delete(route('projects.tickets.links.destroy', [$project, $ticket, $ticketLink]))
|
||||
->assertRedirect(route('projects.tickets.show', [$project, $ticket]))
|
||||
->assertSessionHas('status', 'Relates to link was removed.');
|
||||
->assertSessionHas('status', 'Blocks link was removed.');
|
||||
|
||||
$this->assertDatabaseMissing('ticket_links', [
|
||||
'id' => $ticketLink->id,
|
||||
@@ -193,7 +203,14 @@ class ProjectTicketLinkControllerTest extends TestCase
|
||||
'ticket_id' => $ticket->id,
|
||||
'actor_id' => $user->id,
|
||||
'entry_type' => 'link_removed',
|
||||
'summary' => 'Removed Relates to link to CORE-2.',
|
||||
'summary' => 'Removed Blocks link to CORE-2.',
|
||||
]);
|
||||
|
||||
$this->assertDatabaseHas('ticket_worklog_entries', [
|
||||
'ticket_id' => $linkedTicket->id,
|
||||
'actor_id' => $user->id,
|
||||
'entry_type' => 'link_removed',
|
||||
'summary' => 'Removed Blocked by link to CORE-1.',
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -420,6 +420,7 @@ class ProjectTicketMetadataSettingsControllerTest extends TestCase
|
||||
$project = Project::factory()->create();
|
||||
$ticketLinkType = TicketLinkType::factory()->for($project)->create([
|
||||
'label' => 'Relates to',
|
||||
'counterpart_label' => 'Relates to',
|
||||
'position' => 1,
|
||||
]);
|
||||
|
||||
@@ -428,18 +429,32 @@ class ProjectTicketMetadataSettingsControllerTest extends TestCase
|
||||
$this->actingAs($user)
|
||||
->post(route('projects.settings.metadata.link-types.store', $project), [
|
||||
'name' => 'Blocks',
|
||||
'counterpart_name' => 'Blocked by',
|
||||
])
|
||||
->assertRedirect(route('projects.settings.metadata', $project))
|
||||
->assertSessionHas('status', 'Blocks link type was added.');
|
||||
|
||||
$this->assertDatabaseHas('ticket_link_types', [
|
||||
'project_id' => $project->id,
|
||||
'label' => 'Blocks',
|
||||
'counterpart_label' => 'Blocked by',
|
||||
]);
|
||||
|
||||
$this->actingAs($user)
|
||||
->patch(route('projects.settings.metadata.link-types.update', [$project, $ticketLinkType]), [
|
||||
'name' => 'Depends on',
|
||||
'counterpart_name' => 'Required by',
|
||||
'position' => 2,
|
||||
])
|
||||
->assertRedirect(route('projects.settings.metadata', $project))
|
||||
->assertSessionHas('status', 'Depends on link type was updated.');
|
||||
|
||||
$this->assertDatabaseHas('ticket_link_types', [
|
||||
'id' => $ticketLinkType->id,
|
||||
'label' => 'Depends on',
|
||||
'counterpart_label' => 'Required by',
|
||||
]);
|
||||
|
||||
$this->actingAs($user)
|
||||
->delete(route('projects.settings.metadata.link-types.destroy', [$project, $ticketLinkType]))
|
||||
->assertRedirect(route('projects.settings.metadata', $project))
|
||||
|
||||
Reference in New Issue
Block a user