feat: overhaul project workspace navigation
This commit is contained in:
@@ -57,6 +57,7 @@ class ProjectBacklogTest extends TestCase
|
||||
->component('projects/Backlog')
|
||||
->where('backlogTicketStatuses.0.name', 'Backlog')
|
||||
->where('canEditTickets', false)
|
||||
->where('canManageProject', false)
|
||||
->where('project.id', $project->id)
|
||||
->where('tickets.0.ticket_key', $ticket->ticket_key)
|
||||
->where('canCreateTickets', false),
|
||||
|
||||
@@ -37,7 +37,7 @@ class ProjectBoardControllerTest extends TestCase
|
||||
'name' => 'Doing',
|
||||
'position' => 2,
|
||||
]);
|
||||
$this->createColumnStatus($todoColumn);
|
||||
$todoStatus = $this->createColumnStatus($todoColumn);
|
||||
$doingStatus = $this->createColumnStatus($doingColumn);
|
||||
$backlogStatus = $this->backlogStatus($project);
|
||||
|
||||
@@ -72,6 +72,7 @@ class ProjectBoardControllerTest extends TestCase
|
||||
]);
|
||||
|
||||
$this->syncProjectPermissions($project, $user, ['view']);
|
||||
$this->allowMove($doingStatus, $todoStatus);
|
||||
|
||||
$this->actingAs($user)
|
||||
->get(route('projects.board.index', $project))
|
||||
@@ -88,8 +89,11 @@ class ProjectBoardControllerTest extends TestCase
|
||||
->where('boardColumns.1.tickets.0.assignee.id', $assignee->id)
|
||||
->where('boardColumns.1.tickets.0.assignee.name', $assignee->name)
|
||||
->where('boardColumns.1.tickets.0.assignee.avatar', $assignee->avatar)
|
||||
->where('boardColumns.1.tickets.0.ticket_status.allowed_target_statuses.0.id', $todoStatus->id)
|
||||
->where('boardColumns.1.tickets.0.ticket_status.allowed_target_statuses.0.board_column.id', $todoColumn->id)
|
||||
->where('canCreateTickets', false)
|
||||
->where('canEditTickets', false),
|
||||
->where('canEditTickets', false)
|
||||
->where('canManageProject', false),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
|
||||
namespace Tests\Feature\Projects;
|
||||
|
||||
use App\Actions\ProjectTicketMetadata\InitializeProjectTicketMetadata;
|
||||
use App\Actions\Projects\DuplicateProjectConfiguration;
|
||||
use App\Actions\ProjectTicketMetadata\InitializeProjectTicketMetadata;
|
||||
use App\Models\BoardColumn;
|
||||
use App\Models\Project;
|
||||
use App\Models\ProjectTemplate;
|
||||
@@ -45,16 +45,20 @@ class ProjectBoardSettingsControllerTest extends TestCase
|
||||
$this->syncProjectPermissions($project, $user, ['edit']);
|
||||
|
||||
$this->actingAs($user)
|
||||
->get(route('projects.settings.board', $project))
|
||||
->get(route('projects.settings.index', [
|
||||
'project' => $project,
|
||||
'section' => 'board',
|
||||
]))
|
||||
->assertOk()
|
||||
->assertInertia(fn (Assert $page) => $page
|
||||
->component('projects/settings/Board')
|
||||
->component('projects/Settings')
|
||||
->where('activeSection', 'board')
|
||||
->where('project.id', $project->id)
|
||||
->where('backlogTicketStatuses.0.name', 'Backlog')
|
||||
->where('boardColumns.0.name', 'Todo')
|
||||
->where('ticketStatuses.0.name', 'Backlog')
|
||||
->where('templateState.templateName', 'Starter Workflow')
|
||||
->where('templateState.inheritedAreas.0.value', DuplicateProjectConfiguration::COPY_BOARD_WORKFLOW),
|
||||
->where('sectionData.backlogTicketStatuses.0.name', 'Backlog')
|
||||
->where('sectionData.boardColumns.0.name', 'Todo')
|
||||
->where('sectionData.ticketStatuses.0.name', 'Backlog')
|
||||
->where('sectionData.templateState.templateName', 'Starter Workflow')
|
||||
->where('sectionData.templateState.inheritedAreas.0.value', DuplicateProjectConfiguration::COPY_BOARD_WORKFLOW),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -87,7 +91,7 @@ class ProjectBoardSettingsControllerTest extends TestCase
|
||||
->post(route('projects.settings.board.columns.store', $project), [
|
||||
'name' => 'In Review',
|
||||
])
|
||||
->assertRedirect(route('projects.settings.board', $project))
|
||||
->assertRedirect(route('projects.settings.index', ['project' => $project, 'section' => 'board']))
|
||||
->assertSessionHas('status', 'In Review was added to the board workflow.');
|
||||
|
||||
$this->assertDatabaseHas('board_columns', [
|
||||
@@ -131,7 +135,7 @@ class ProjectBoardSettingsControllerTest extends TestCase
|
||||
'name' => 'Ready for QA',
|
||||
'position' => 1,
|
||||
])
|
||||
->assertRedirect(route('projects.settings.board', $project))
|
||||
->assertRedirect(route('projects.settings.index', ['project' => $project, 'section' => 'board']))
|
||||
->assertSessionHas('status', 'Ready for QA was updated.');
|
||||
|
||||
$firstColumn->refresh();
|
||||
@@ -168,7 +172,7 @@ class ProjectBoardSettingsControllerTest extends TestCase
|
||||
|
||||
$this->actingAs($user)
|
||||
->delete(route('projects.settings.board.columns.destroy', [$project, $secondColumn]))
|
||||
->assertRedirect(route('projects.settings.board', $project))
|
||||
->assertRedirect(route('projects.settings.index', ['project' => $project, 'section' => 'board']))
|
||||
->assertSessionHas('status', 'In Progress was removed from the board workflow.');
|
||||
|
||||
$firstColumn->refresh();
|
||||
@@ -209,7 +213,7 @@ class ProjectBoardSettingsControllerTest extends TestCase
|
||||
'board_column_id' => $boardColumn->id,
|
||||
'name' => 'Ready for QA',
|
||||
])
|
||||
->assertRedirect(route('projects.settings.board', $project))
|
||||
->assertRedirect(route('projects.settings.index', ['project' => $project, 'section' => 'board']))
|
||||
->assertSessionHas('status', 'Ready for QA was added to Todo.');
|
||||
|
||||
$newStatus = TicketStatus::query()
|
||||
@@ -225,7 +229,7 @@ class ProjectBoardSettingsControllerTest extends TestCase
|
||||
->patch(route('projects.settings.board.statuses.update', [$project, $newStatus]), [
|
||||
'name' => 'QA Ready',
|
||||
])
|
||||
->assertRedirect(route('projects.settings.board', $project))
|
||||
->assertRedirect(route('projects.settings.index', ['project' => $project, 'section' => 'board']))
|
||||
->assertSessionHas('status', 'QA Ready was updated.');
|
||||
|
||||
$newStatus->refresh();
|
||||
@@ -240,7 +244,7 @@ class ProjectBoardSettingsControllerTest extends TestCase
|
||||
|
||||
$this->actingAs($user)
|
||||
->delete(route('projects.settings.board.statuses.destroy', [$project, $newStatus]))
|
||||
->assertRedirect(route('projects.settings.board', $project))
|
||||
->assertRedirect(route('projects.settings.index', ['project' => $project, 'section' => 'board']))
|
||||
->assertSessionHas('status', 'QA Ready was removed from Todo.');
|
||||
|
||||
$this->assertDatabaseMissing('ticket_statuses', [
|
||||
@@ -280,7 +284,7 @@ class ProjectBoardSettingsControllerTest extends TestCase
|
||||
->patch(route('projects.settings.board.statuses.transitions.update', [$project, $sourceStatus]), [
|
||||
'allowed_ticket_status_ids' => [$reviewStatus->id, $doneStatus->id],
|
||||
])
|
||||
->assertRedirect(route('projects.settings.board', $project))
|
||||
->assertRedirect(route('projects.settings.index', ['project' => $project, 'section' => 'board']))
|
||||
->assertSessionHas('status', 'Progress move targets were updated.');
|
||||
|
||||
$this->assertEqualsCanonicalizing(
|
||||
@@ -290,7 +294,7 @@ class ProjectBoardSettingsControllerTest extends TestCase
|
||||
|
||||
$this->actingAs($user)
|
||||
->patch(route('projects.settings.board.statuses.transitions.update', [$project, $sourceStatus]), [])
|
||||
->assertRedirect(route('projects.settings.board', $project))
|
||||
->assertRedirect(route('projects.settings.index', ['project' => $project, 'section' => 'board']))
|
||||
->assertSessionHas('status', 'Progress move targets were updated.');
|
||||
|
||||
$this->assertCount(0, $sourceStatus->allowedTargetStatuses()->get());
|
||||
|
||||
@@ -174,7 +174,7 @@ class ProjectControllerTest extends TestCase
|
||||
|
||||
$project = Project::query()->firstOrFail();
|
||||
|
||||
$response->assertRedirect(route('projects.show', $project));
|
||||
$response->assertRedirect(route('projects.board.index', $project));
|
||||
|
||||
$this->assertDatabaseHas('projects', [
|
||||
'name' => 'Kanban Foundation',
|
||||
@@ -281,7 +281,7 @@ class ProjectControllerTest extends TestCase
|
||||
|
||||
$project = Project::query()->where('key', 'TMP1')->firstOrFail();
|
||||
|
||||
$response->assertRedirect(route('projects.show', $project));
|
||||
$response->assertRedirect(route('projects.board.index', $project));
|
||||
|
||||
$project->refresh();
|
||||
|
||||
@@ -412,7 +412,7 @@ class ProjectControllerTest extends TestCase
|
||||
|
||||
$project = Project::query()->where('key', 'DUPE')->firstOrFail();
|
||||
|
||||
$response->assertRedirect(route('projects.show', $project));
|
||||
$response->assertRedirect(route('projects.board.index', $project));
|
||||
|
||||
$this->assertSame('Duplicated Project', $project->name);
|
||||
$this->assertFalse($project->show_author);
|
||||
@@ -469,9 +469,9 @@ class ProjectControllerTest extends TestCase
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure authenticated users can view the project hub page.
|
||||
* Ensure authenticated users enter a project through its board.
|
||||
*/
|
||||
public function test_authenticated_users_can_view_a_project_hub_page(): void
|
||||
public function test_authenticated_users_are_redirected_to_the_project_board(): void
|
||||
{
|
||||
$user = User::factory()->create();
|
||||
$project = Project::factory()->create([
|
||||
@@ -482,13 +482,7 @@ class ProjectControllerTest extends TestCase
|
||||
|
||||
$this->actingAs($user)
|
||||
->get(route('projects.show', $project))
|
||||
->assertOk()
|
||||
->assertInertia(fn (Assert $page) => $page
|
||||
->component('projects/Show')
|
||||
->where('canManageAccess', false)
|
||||
->where('project.id', $project->id)
|
||||
->where('project.key', 'CORE'),
|
||||
);
|
||||
->assertRedirect(route('projects.board.index', $project));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -507,11 +501,7 @@ class ProjectControllerTest extends TestCase
|
||||
|
||||
$this->actingAs($user)
|
||||
->get(route('projects.show', $project))
|
||||
->assertOk()
|
||||
->assertInertia(fn (Assert $page) => $page
|
||||
->component('projects/Show')
|
||||
->where('project.id', $project->id),
|
||||
);
|
||||
->assertRedirect(route('projects.board.index', $project));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -544,14 +534,10 @@ class ProjectControllerTest extends TestCase
|
||||
|
||||
$this->actingAs($user)
|
||||
->get(route('projects.access', $project))
|
||||
->assertOk()
|
||||
->assertInertia(fn (Assert $page) => $page
|
||||
->component('projects/Access')
|
||||
->where('project.id', $project->id)
|
||||
->has('users', 1)
|
||||
->has('teams', 1)
|
||||
->where('permissionOptions.0.value', 'project:read'),
|
||||
);
|
||||
->assertRedirect(route('projects.settings.index', [
|
||||
'project' => $project,
|
||||
'section' => 'access',
|
||||
]));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -566,12 +552,10 @@ class ProjectControllerTest extends TestCase
|
||||
|
||||
$this->actingAs($admin)
|
||||
->get(route('projects.settings.general', $project))
|
||||
->assertOk()
|
||||
->assertInertia(fn (Assert $page) => $page
|
||||
->component('projects/settings/General')
|
||||
->where('project.id', $project->id)
|
||||
->where('project.key', 'CORE'),
|
||||
);
|
||||
->assertRedirect(route('projects.settings.index', [
|
||||
'project' => $project,
|
||||
'section' => 'general',
|
||||
]));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -588,11 +572,10 @@ class ProjectControllerTest extends TestCase
|
||||
|
||||
$this->actingAs($user)
|
||||
->get(route('projects.settings.general', $project))
|
||||
->assertOk()
|
||||
->assertInertia(fn (Assert $page) => $page
|
||||
->component('projects/settings/General')
|
||||
->where('project.id', $project->id),
|
||||
);
|
||||
->assertRedirect(route('projects.settings.index', [
|
||||
'project' => $project,
|
||||
'section' => 'general',
|
||||
]));
|
||||
}
|
||||
|
||||
public function test_project_admins_can_switch_projects_to_another_template(): void
|
||||
@@ -652,7 +635,10 @@ class ProjectControllerTest extends TestCase
|
||||
'confirmation_key' => 'CORE',
|
||||
'source_template_id' => $newTemplate->id,
|
||||
])
|
||||
->assertRedirect(route('projects.settings.general', $project))
|
||||
->assertRedirect(route('projects.settings.index', [
|
||||
'project' => $project,
|
||||
'section' => 'general',
|
||||
]))
|
||||
->assertSessionHas('status', 'Core Platform now uses the Operations Template template.');
|
||||
|
||||
$project->refresh();
|
||||
@@ -707,7 +693,10 @@ class ProjectControllerTest extends TestCase
|
||||
->post(route('projects.settings.general.reset-template', $project), [
|
||||
'confirmation_key' => 'CORE',
|
||||
])
|
||||
->assertRedirect(route('projects.settings.general', $project))
|
||||
->assertRedirect(route('projects.settings.index', [
|
||||
'project' => $project,
|
||||
'section' => 'general',
|
||||
]))
|
||||
->assertSessionHas('status', 'Core Platform was reset to the Workflow Template template baseline.');
|
||||
|
||||
$project->refresh();
|
||||
@@ -878,7 +867,10 @@ class ProjectControllerTest extends TestCase
|
||||
->patch(route('projects.permissions.users.update', [$project, $member]), [
|
||||
'permissions' => ['project:read', 'project:write'],
|
||||
])
|
||||
->assertRedirect(route('projects.access', $project))
|
||||
->assertRedirect(route('projects.settings.index', [
|
||||
'project' => $project,
|
||||
'section' => 'access',
|
||||
]))
|
||||
->assertSessionHas('status', 'Regular User permissions were updated.');
|
||||
|
||||
$this->assertDatabaseHas('permission_assignments', [
|
||||
@@ -917,7 +909,10 @@ class ProjectControllerTest extends TestCase
|
||||
->patch(route('projects.permissions.teams.update', [$project, $team]), [
|
||||
'permissions' => ['project:read', 'project:write'],
|
||||
])
|
||||
->assertRedirect(route('projects.access', $project))
|
||||
->assertRedirect(route('projects.settings.index', [
|
||||
'project' => $project,
|
||||
'section' => 'access',
|
||||
]))
|
||||
->assertSessionHas('status', 'Delivery Team permissions were updated.');
|
||||
|
||||
$this->assertDatabaseHas('permission_assignments', [
|
||||
|
||||
@@ -55,37 +55,41 @@ class ProjectNotificationSettingsControllerTest extends TestCase
|
||||
$this->syncProjectPermissions($project, $user, ['edit']);
|
||||
|
||||
$this->actingAs($user)
|
||||
->get(route('projects.settings.notifications', $project))
|
||||
->get(route('projects.settings.index', [
|
||||
'project' => $project,
|
||||
'section' => 'notifications',
|
||||
]))
|
||||
->assertOk()
|
||||
->assertInertia(fn (Assert $page) => $page
|
||||
->component('projects/settings/Notifications')
|
||||
->component('projects/Settings')
|
||||
->where('activeSection', 'notifications')
|
||||
->where('project.id', $project->id)
|
||||
->where('notificationActions.0.name', 'Discord alerts')
|
||||
->where('notificationActions.0.channel', NotificationChannel::Discord->value)
|
||||
->where('notificationActions.0.channel_configuration.allowed_mentions.0', 'users')
|
||||
->where('notificationActions.0.channel_configuration.allowed_mentions.1', 'roles')
|
||||
->where('notificationActions.0.event_type', NotificationEventType::TicketStatusChanged->value)
|
||||
->where('notificationActions.0.enabled', true)
|
||||
->where('notificationActions.0.has_delivery_url', true)
|
||||
->where('notificationActions.0.filters.0.attribute', 'status.to.label')
|
||||
->where('notificationActions.0.filters.0.operator', 'equals')
|
||||
->where('notificationActions.0.filters.0.value', 'Doing')
|
||||
->where('notificationActions.0.updated_at', $notificationAction->updated_at?->toISOString())
|
||||
->where('channelOptions.0.value', NotificationChannel::Discord->value)
|
||||
->where('eventTypeOptions.0.value', NotificationEventType::TicketStatusChanged->value)
|
||||
->where('eventTypeOptions.1.value', NotificationEventType::TicketCreate->value)
|
||||
->where('eventTypeOptions.2.value', NotificationEventType::TicketAssigneeChanged->value)
|
||||
->where('discordAllowedMentionOptions.0.value', 'everyone')
|
||||
->where('discordAllowedMentionOptions.2.value', 'users')
|
||||
->where('filterAttributeOptions.0.value', 'project.id')
|
||||
->where('filterOperatorOptions.0.value', 'equals')
|
||||
->where('templateVariables.0.value', '$url')
|
||||
->where('templateVariables.6.value', '$actor_name')
|
||||
->where('templateVariables.7.value', '$assignee_from')
|
||||
->where('templateVariables.8.value', '$assignee_to')
|
||||
->where('templateState.templateName', 'Alert Template')
|
||||
->where('templateState.inheritedAreas.0.value', DuplicateProjectConfiguration::COPY_NOTIFICATION_ACTIONS)
|
||||
->missing('notificationActions.0.delivery_url'),
|
||||
->where('sectionData.notificationActions.0.name', 'Discord alerts')
|
||||
->where('sectionData.notificationActions.0.channel', NotificationChannel::Discord->value)
|
||||
->where('sectionData.notificationActions.0.channel_configuration.allowed_mentions.0', 'users')
|
||||
->where('sectionData.notificationActions.0.channel_configuration.allowed_mentions.1', 'roles')
|
||||
->where('sectionData.notificationActions.0.event_type', NotificationEventType::TicketStatusChanged->value)
|
||||
->where('sectionData.notificationActions.0.enabled', true)
|
||||
->where('sectionData.notificationActions.0.has_delivery_url', true)
|
||||
->where('sectionData.notificationActions.0.filters.0.attribute', 'status.to.label')
|
||||
->where('sectionData.notificationActions.0.filters.0.operator', 'equals')
|
||||
->where('sectionData.notificationActions.0.filters.0.value', 'Doing')
|
||||
->where('sectionData.notificationActions.0.updated_at', $notificationAction->updated_at?->toISOString())
|
||||
->where('sectionData.channelOptions.0.value', NotificationChannel::Discord->value)
|
||||
->where('sectionData.eventTypeOptions.0.value', NotificationEventType::TicketStatusChanged->value)
|
||||
->where('sectionData.eventTypeOptions.1.value', NotificationEventType::TicketCreate->value)
|
||||
->where('sectionData.eventTypeOptions.2.value', NotificationEventType::TicketAssigneeChanged->value)
|
||||
->where('sectionData.discordAllowedMentionOptions.0.value', 'everyone')
|
||||
->where('sectionData.discordAllowedMentionOptions.2.value', 'users')
|
||||
->where('sectionData.filterAttributeOptions.0.value', 'project.id')
|
||||
->where('sectionData.filterOperatorOptions.0.value', 'equals')
|
||||
->where('sectionData.templateVariables.0.value', '$url')
|
||||
->where('sectionData.templateVariables.6.value', '$actor_name')
|
||||
->where('sectionData.templateVariables.7.value', '$assignee_from')
|
||||
->where('sectionData.templateVariables.8.value', '$assignee_to')
|
||||
->where('sectionData.templateState.templateName', 'Alert Template')
|
||||
->where('sectionData.templateState.inheritedAreas.0.value', DuplicateProjectConfiguration::COPY_NOTIFICATION_ACTIONS)
|
||||
->missing('sectionData.notificationActions.0.delivery_url'),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -133,7 +137,7 @@ class ProjectNotificationSettingsControllerTest extends TestCase
|
||||
], JSON_THROW_ON_ERROR),
|
||||
'message_template' => '$ticket_key moved from $status_from to $status_to by $actor_name: $url',
|
||||
])
|
||||
->assertRedirect(route('projects.settings.notifications', $project))
|
||||
->assertRedirect(route('projects.settings.index', ['project' => $project, 'section' => 'notifications']))
|
||||
->assertSessionHas('status', 'Discord alerts notification was added.');
|
||||
|
||||
$notificationAction = NotificationAction::query()->firstOrFail();
|
||||
@@ -195,7 +199,7 @@ class ProjectNotificationSettingsControllerTest extends TestCase
|
||||
], JSON_THROW_ON_ERROR),
|
||||
'message_template' => '$ticket_key moved to $status_to: $url',
|
||||
])
|
||||
->assertRedirect(route('projects.settings.notifications', $project))
|
||||
->assertRedirect(route('projects.settings.index', ['project' => $project, 'section' => 'notifications']))
|
||||
->assertSessionHas('status', 'Ops escalation alerts notification was updated.');
|
||||
|
||||
$notificationAction->refresh();
|
||||
@@ -250,7 +254,7 @@ class ProjectNotificationSettingsControllerTest extends TestCase
|
||||
'delivery_url' => '',
|
||||
'message_template' => '',
|
||||
])
|
||||
->assertRedirect(route('projects.settings.notifications', $project));
|
||||
->assertRedirect(route('projects.settings.index', ['project' => $project, 'section' => 'notifications']));
|
||||
|
||||
$this->assertFalse($notificationAction->fresh()->enabled);
|
||||
|
||||
@@ -263,7 +267,7 @@ class ProjectNotificationSettingsControllerTest extends TestCase
|
||||
'delivery_url' => '',
|
||||
'message_template' => '',
|
||||
])
|
||||
->assertRedirect(route('projects.settings.notifications', $project));
|
||||
->assertRedirect(route('projects.settings.index', ['project' => $project, 'section' => 'notifications']));
|
||||
|
||||
$this->assertTrue($notificationAction->fresh()->enabled);
|
||||
}
|
||||
@@ -293,7 +297,7 @@ class ProjectNotificationSettingsControllerTest extends TestCase
|
||||
'delivery_url' => 'https://hooks.example.test/kanbangit/replaced-secret',
|
||||
'message_template' => '',
|
||||
])
|
||||
->assertRedirect(route('projects.settings.notifications', $project));
|
||||
->assertRedirect(route('projects.settings.index', ['project' => $project, 'section' => 'notifications']));
|
||||
|
||||
$this->assertSame(
|
||||
'https://hooks.example.test/kanbangit/replaced-secret',
|
||||
@@ -318,7 +322,7 @@ class ProjectNotificationSettingsControllerTest extends TestCase
|
||||
|
||||
$this->actingAs($user)
|
||||
->delete(route('projects.settings.notifications.destroy', [$project, $notificationAction]))
|
||||
->assertRedirect(route('projects.settings.notifications', $project))
|
||||
->assertRedirect(route('projects.settings.index', ['project' => $project, 'section' => 'notifications']))
|
||||
->assertSessionHas('status', 'Webhook alerts notification was deleted.');
|
||||
|
||||
$this->assertModelMissing($notificationAction);
|
||||
@@ -422,12 +426,15 @@ class ProjectNotificationSettingsControllerTest extends TestCase
|
||||
$this->syncProjectPermissions($project, $user, ['edit']);
|
||||
|
||||
$this->actingAs($user)
|
||||
->get(route('projects.settings.notifications', $project))
|
||||
->get(route('projects.settings.index', [
|
||||
'project' => $project,
|
||||
'section' => 'notifications',
|
||||
]))
|
||||
->assertOk()
|
||||
->assertInertia(fn (Assert $page) => $page
|
||||
->component('projects/settings/Notifications')
|
||||
->where('notificationActions.0.has_delivery_url', true)
|
||||
->missing('notificationActions.0.delivery_url'),
|
||||
->component('projects/Settings')
|
||||
->where('sectionData.notificationActions.0.has_delivery_url', true)
|
||||
->missing('sectionData.notificationActions.0.delivery_url'),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,16 +50,20 @@ class ProjectRepositorySettingsControllerTest extends TestCase
|
||||
$this->syncProjectPermissions($project, $user, ['edit']);
|
||||
|
||||
$this->actingAs($user)
|
||||
->get(route('projects.settings.repositories', $project))
|
||||
->get(route('projects.settings.index', [
|
||||
'project' => $project,
|
||||
'section' => 'repositories',
|
||||
]))
|
||||
->assertOk()
|
||||
->assertInertia(fn (Assert $page) => $page
|
||||
->component('projects/settings/Repositories')
|
||||
->component('projects/Settings')
|
||||
->where('activeSection', 'repositories')
|
||||
->where('project.id', $project->id)
|
||||
->where('repositoryConnections.0.repository_full_name', 'core-team/kanbangit')
|
||||
->where('repositoryConnections.0.has_personal_access', false)
|
||||
->where('availableRepositorySources', [])
|
||||
->where('templateState.templateName', 'Connected Template')
|
||||
->where('templateState.inheritedAreas.0.value', DuplicateProjectConfiguration::COPY_REPOSITORY_CONNECTIONS),
|
||||
->where('sectionData.repositoryConnections.0.repository_full_name', 'core-team/kanbangit')
|
||||
->where('sectionData.repositoryConnections.0.has_personal_access', false)
|
||||
->where('sectionData.availableRepositorySources', [])
|
||||
->where('sectionData.templateState.templateName', 'Connected Template')
|
||||
->where('sectionData.templateState.inheritedAreas.0.value', DuplicateProjectConfiguration::COPY_REPOSITORY_CONNECTIONS),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -111,7 +115,7 @@ class ProjectRepositorySettingsControllerTest extends TestCase
|
||||
'provider_repository_id' => '501',
|
||||
'user_git_connection_id' => $userGitConnection->id,
|
||||
])
|
||||
->assertRedirect(route('projects.settings.repositories', $project))
|
||||
->assertRedirect(route('projects.settings.index', ['project' => $project, 'section' => 'repositories']))
|
||||
->assertSessionHas('status', 'core-team/kanbangit was linked to '.$project->name.'.');
|
||||
|
||||
$this->assertDatabaseHas('repository_connections', [
|
||||
@@ -192,7 +196,7 @@ class ProjectRepositorySettingsControllerTest extends TestCase
|
||||
'provider_repository_id' => '902',
|
||||
'user_git_connection_id' => $userGitConnection->id,
|
||||
])
|
||||
->assertRedirect(route('projects.settings.repositories', $project))
|
||||
->assertRedirect(route('projects.settings.index', ['project' => $project, 'section' => 'repositories']))
|
||||
->assertSessionHas('status', 'infra/platform connection was updated.');
|
||||
|
||||
$this->assertDatabaseHas('repository_connections', [
|
||||
@@ -220,7 +224,7 @@ class ProjectRepositorySettingsControllerTest extends TestCase
|
||||
|
||||
$this->actingAs($user)
|
||||
->delete(route('projects.settings.repositories.destroy', [$project, $repositoryConnection]))
|
||||
->assertRedirect(route('projects.settings.repositories', $project))
|
||||
->assertRedirect(route('projects.settings.index', ['project' => $project, 'section' => 'repositories']))
|
||||
->assertSessionHas('status', 'core-team/kanbangit was removed from the project.');
|
||||
|
||||
$this->assertDatabaseMissing('repository_connections', [
|
||||
|
||||
@@ -0,0 +1,148 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Feature\Projects;
|
||||
|
||||
use App\Actions\Git\GitProviderKey;
|
||||
use App\Actions\ProjectTicketMetadata\InitializeProjectTicketMetadata;
|
||||
use App\Models\BoardColumn;
|
||||
use App\Models\Project;
|
||||
use App\Models\Team;
|
||||
use App\Models\TicketStatus;
|
||||
use App\Models\User;
|
||||
use App\Models\UserGitConnection;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Inertia\Testing\AssertableInertia as Assert;
|
||||
use Tests\TestCase;
|
||||
|
||||
class ProjectSettingsControllerTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
public function test_project_admins_can_open_each_settings_section(): void
|
||||
{
|
||||
$user = User::factory()->create();
|
||||
$project = Project::factory()->create(['key' => 'CORE']);
|
||||
$team = Team::factory()->create();
|
||||
|
||||
app(InitializeProjectTicketMetadata::class)->handle($project);
|
||||
$boardColumn = BoardColumn::factory()->for($project)->create([
|
||||
'name' => 'In progress',
|
||||
]);
|
||||
TicketStatus::factory()->for($project)->for($boardColumn)->create([
|
||||
'name' => 'In progress',
|
||||
]);
|
||||
|
||||
$this->syncProjectPermissions($project, $user, ['admin']);
|
||||
|
||||
$expectedSectionProps = [
|
||||
'general' => 'sourceTemplates',
|
||||
'board' => 'boardColumns',
|
||||
'metadata' => 'ticketTypes',
|
||||
'repositories' => 'repositoryConnections',
|
||||
'notifications' => 'notificationActions',
|
||||
'access' => 'teams',
|
||||
];
|
||||
|
||||
foreach ($expectedSectionProps as $section => $expectedProp) {
|
||||
$this->actingAs($user)
|
||||
->get(route('projects.settings.index', [
|
||||
'project' => $project,
|
||||
'section' => $section,
|
||||
]))
|
||||
->assertOk()
|
||||
->assertInertia(fn (Assert $page) => $page
|
||||
->component('projects/Settings')
|
||||
->where('activeSection', $section)
|
||||
->where('project.id', $project->id)
|
||||
->has("sectionData.{$expectedProp}"),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public function test_missing_and_invalid_sections_fall_back_to_general(): void
|
||||
{
|
||||
$user = User::factory()->create();
|
||||
$project = Project::factory()->create();
|
||||
|
||||
$this->syncProjectPermissions($project, $user, ['admin']);
|
||||
|
||||
foreach ([null, 'unknown'] as $section) {
|
||||
$parameters = ['project' => $project];
|
||||
|
||||
if ($section !== null) {
|
||||
$parameters['section'] = $section;
|
||||
}
|
||||
|
||||
$this->actingAs($user)
|
||||
->get(route('projects.settings.index', $parameters))
|
||||
->assertOk()
|
||||
->assertInertia(fn (Assert $page) => $page
|
||||
->component('projects/Settings')
|
||||
->where('activeSection', 'general')
|
||||
->has('sectionData.sourceTemplates'),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public function test_users_without_project_admin_permission_cannot_open_settings(): void
|
||||
{
|
||||
$user = User::factory()->create();
|
||||
$project = Project::factory()->create();
|
||||
|
||||
$this->syncProjectPermissions($project, $user, ['view']);
|
||||
|
||||
$this->actingAs($user)
|
||||
->get(route('projects.settings.index', $project))
|
||||
->assertForbidden();
|
||||
}
|
||||
|
||||
public function test_repository_providers_are_not_queried_outside_the_repository_section(): void
|
||||
{
|
||||
Http::preventStrayRequests();
|
||||
|
||||
$user = User::factory()->create();
|
||||
$project = Project::factory()->create();
|
||||
|
||||
UserGitConnection::factory()->for($user)->create([
|
||||
'base_url' => 'https://git.example.test',
|
||||
'provider' => GitProviderKey::Gitea,
|
||||
]);
|
||||
$this->syncProjectPermissions($project, $user, ['admin']);
|
||||
|
||||
$this->actingAs($user)
|
||||
->get(route('projects.settings.index', [
|
||||
'project' => $project,
|
||||
'section' => 'general',
|
||||
]))
|
||||
->assertOk();
|
||||
|
||||
Http::assertNothingSent();
|
||||
}
|
||||
|
||||
public function test_legacy_settings_urls_redirect_to_the_matching_section(): void
|
||||
{
|
||||
$user = User::factory()->create();
|
||||
$project = Project::factory()->create();
|
||||
|
||||
$this->syncProjectPermissions($project, $user, ['admin']);
|
||||
|
||||
$legacyRoutes = [
|
||||
'projects.settings.general' => 'general',
|
||||
'projects.settings.board' => 'board',
|
||||
'projects.settings.metadata' => 'metadata',
|
||||
'projects.settings.repositories' => 'repositories',
|
||||
'projects.settings.notifications' => 'notifications',
|
||||
'projects.access' => 'access',
|
||||
];
|
||||
|
||||
foreach ($legacyRoutes as $legacyRoute => $section) {
|
||||
$this->actingAs($user)
|
||||
->get(route($legacyRoute, $project))
|
||||
->assertRedirect(route('projects.settings.index', [
|
||||
'project' => $project,
|
||||
'section' => $section,
|
||||
]));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -403,6 +403,7 @@ class ProjectTicketControllerTest extends TestCase
|
||||
->assertOk()
|
||||
->assertInertia(fn (Assert $page) => $page
|
||||
->component('projects/TicketCreate')
|
||||
->where('canManageProject', false)
|
||||
->where('defaultTicketStatusId', $boardStatus->id)
|
||||
->where('parentTicketOptions.0.ticket_key', $parentTicket->ticket_key)
|
||||
->where('project.id', $project->id)
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
|
||||
namespace Tests\Feature\Projects;
|
||||
|
||||
use App\Actions\ProjectTicketMetadata\InitializeProjectTicketMetadata;
|
||||
use App\Actions\Projects\DuplicateProjectConfiguration;
|
||||
use App\Actions\ProjectTicketMetadata\InitializeProjectTicketMetadata;
|
||||
use App\Models\Project;
|
||||
use App\Models\ProjectTemplate;
|
||||
use App\Models\Ticket;
|
||||
@@ -60,19 +60,23 @@ class ProjectTicketMetadataSettingsControllerTest extends TestCase
|
||||
$this->syncProjectPermissions($project, $user, ['edit']);
|
||||
|
||||
$this->actingAs($user)
|
||||
->get(route('projects.settings.metadata', $project))
|
||||
->get(route('projects.settings.index', [
|
||||
'project' => $project,
|
||||
'section' => 'metadata',
|
||||
]))
|
||||
->assertOk()
|
||||
->assertInertia(fn (Assert $page) => $page
|
||||
->component('projects/settings/Metadata')
|
||||
->component('projects/Settings')
|
||||
->where('activeSection', 'metadata')
|
||||
->where('project.show_author', true)
|
||||
->where('project.show_story_points', false)
|
||||
->where('ticketTypes.0.name', 'Task')
|
||||
->where('ticketTypes.1.name', 'Release')
|
||||
->where('ticketTypes.1.is_parent', true)
|
||||
->where('ticketTypes.2.name', 'Bug')
|
||||
->where('ticketLinkTypes.0.label', 'Relates to')
|
||||
->where('templateState.templateName', 'Issue Tracker Template')
|
||||
->where('templateState.overriddenAreas.0.value', DuplicateProjectConfiguration::COPY_PROJECT_SETTINGS),
|
||||
->where('sectionData.ticketTypes.0.name', 'Task')
|
||||
->where('sectionData.ticketTypes.1.name', 'Release')
|
||||
->where('sectionData.ticketTypes.1.is_parent', true)
|
||||
->where('sectionData.ticketTypes.2.name', 'Bug')
|
||||
->where('sectionData.ticketLinkTypes.0.label', 'Relates to')
|
||||
->where('sectionData.templateState.templateName', 'Issue Tracker Template')
|
||||
->where('sectionData.templateState.overriddenAreas.0.value', DuplicateProjectConfiguration::COPY_PROJECT_SETTINGS),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -91,7 +95,7 @@ class ProjectTicketMetadataSettingsControllerTest extends TestCase
|
||||
'is_parent' => '1',
|
||||
'name' => 'Bug',
|
||||
])
|
||||
->assertRedirect(route('projects.settings.metadata', $project))
|
||||
->assertRedirect(route('projects.settings.index', ['project' => $project, 'section' => 'metadata']))
|
||||
->assertSessionHas('status', 'Bug type was added.');
|
||||
|
||||
$this->assertDatabaseHas('ticket_types', [
|
||||
@@ -124,7 +128,7 @@ class ProjectTicketMetadataSettingsControllerTest extends TestCase
|
||||
'name' => 'Standard Release',
|
||||
'position' => 2,
|
||||
])
|
||||
->assertRedirect(route('projects.settings.metadata', $project))
|
||||
->assertRedirect(route('projects.settings.index', ['project' => $project, 'section' => 'metadata']))
|
||||
->assertSessionHas('status', 'Standard Release type was updated.');
|
||||
|
||||
$this->assertDatabaseHas('ticket_types', [
|
||||
@@ -152,7 +156,7 @@ class ProjectTicketMetadataSettingsControllerTest extends TestCase
|
||||
'show_author' => false,
|
||||
'show_story_points' => false,
|
||||
])
|
||||
->assertRedirect(route('projects.settings.metadata', $project))
|
||||
->assertRedirect(route('projects.settings.index', ['project' => $project, 'section' => 'metadata']))
|
||||
->assertSessionHas('status', 'Settings have been updated.');
|
||||
|
||||
$this->assertDatabaseHas('projects', [
|
||||
@@ -196,7 +200,7 @@ class ProjectTicketMetadataSettingsControllerTest extends TestCase
|
||||
|
||||
$this->actingAs($user)
|
||||
->delete(route('projects.settings.metadata.types.destroy', [$project, $ticketType]))
|
||||
->assertRedirect(route('projects.settings.metadata', $project))
|
||||
->assertRedirect(route('projects.settings.index', ['project' => $project, 'section' => 'metadata']))
|
||||
->assertSessionHas('status', 'The last ticket type cannot be deleted.');
|
||||
|
||||
$this->assertDatabaseHas('ticket_types', [
|
||||
@@ -248,7 +252,7 @@ class ProjectTicketMetadataSettingsControllerTest extends TestCase
|
||||
->delete(route('projects.settings.metadata.types.destroy', [$project, $taskType]), [
|
||||
'replacement_ticket_type_id' => $releaseType->id,
|
||||
])
|
||||
->assertRedirect(route('projects.settings.metadata', $project))
|
||||
->assertRedirect(route('projects.settings.index', ['project' => $project, 'section' => 'metadata']))
|
||||
->assertSessionHas('status', 'Task type was removed.');
|
||||
|
||||
$this->assertDatabaseMissing('ticket_types', [
|
||||
@@ -327,7 +331,7 @@ class ProjectTicketMetadataSettingsControllerTest extends TestCase
|
||||
->delete(route('projects.settings.metadata.types.destroy', [$project, $releaseType]), [
|
||||
'replacement_ticket_type_id' => $epicType->id,
|
||||
])
|
||||
->assertRedirect(route('projects.settings.metadata', $project))
|
||||
->assertRedirect(route('projects.settings.index', ['project' => $project, 'section' => 'metadata']))
|
||||
->assertSessionHas('status', 'Release type was removed.');
|
||||
|
||||
$this->assertDatabaseHas('tickets', [
|
||||
@@ -354,19 +358,19 @@ class ProjectTicketMetadataSettingsControllerTest extends TestCase
|
||||
->post(route('projects.settings.metadata.priorities.store', $project), [
|
||||
'name' => 'Low',
|
||||
])
|
||||
->assertRedirect(route('projects.settings.metadata', $project));
|
||||
->assertRedirect(route('projects.settings.index', ['project' => $project, 'section' => 'metadata']));
|
||||
|
||||
$this->actingAs($user)
|
||||
->patch(route('projects.settings.metadata.priorities.update', [$project, $priority]), [
|
||||
'name' => 'Urgent',
|
||||
'position' => 2,
|
||||
])
|
||||
->assertRedirect(route('projects.settings.metadata', $project))
|
||||
->assertRedirect(route('projects.settings.index', ['project' => $project, 'section' => 'metadata']))
|
||||
->assertSessionHas('status', 'Urgent priority was updated.');
|
||||
|
||||
$this->actingAs($user)
|
||||
->delete(route('projects.settings.metadata.priorities.destroy', [$project, $priority]))
|
||||
->assertRedirect(route('projects.settings.metadata', $project))
|
||||
->assertRedirect(route('projects.settings.index', ['project' => $project, 'section' => 'metadata']))
|
||||
->assertSessionHas('status', 'Urgent priority was removed.');
|
||||
|
||||
$this->assertDatabaseMissing('ticket_priorities', [
|
||||
@@ -394,7 +398,7 @@ class ProjectTicketMetadataSettingsControllerTest extends TestCase
|
||||
'label' => 'Reviewer',
|
||||
'field_type' => 'user',
|
||||
])
|
||||
->assertRedirect(route('projects.settings.metadata', $project))
|
||||
->assertRedirect(route('projects.settings.index', ['project' => $project, 'section' => 'metadata']))
|
||||
->assertSessionHas('status', 'Reviewer field was added.');
|
||||
|
||||
$this->actingAs($user)
|
||||
@@ -403,7 +407,7 @@ class ProjectTicketMetadataSettingsControllerTest extends TestCase
|
||||
'field_type' => 'number',
|
||||
'position' => 2,
|
||||
])
|
||||
->assertRedirect(route('projects.settings.metadata', $project))
|
||||
->assertRedirect(route('projects.settings.index', ['project' => $project, 'section' => 'metadata']))
|
||||
->assertSessionHas('status', 'Risk score field was updated.');
|
||||
|
||||
$this->assertDatabaseHas('ticket_field_definitions', [
|
||||
@@ -453,7 +457,7 @@ class ProjectTicketMetadataSettingsControllerTest extends TestCase
|
||||
'name' => 'Blocks',
|
||||
'counterpart_name' => 'Blocked by',
|
||||
])
|
||||
->assertRedirect(route('projects.settings.metadata', $project))
|
||||
->assertRedirect(route('projects.settings.index', ['project' => $project, 'section' => 'metadata']))
|
||||
->assertSessionHas('status', 'Blocks link type was added.');
|
||||
|
||||
$this->assertDatabaseHas('ticket_link_types', [
|
||||
@@ -468,7 +472,7 @@ class ProjectTicketMetadataSettingsControllerTest extends TestCase
|
||||
'counterpart_name' => 'Required by',
|
||||
'position' => 2,
|
||||
])
|
||||
->assertRedirect(route('projects.settings.metadata', $project))
|
||||
->assertRedirect(route('projects.settings.index', ['project' => $project, 'section' => 'metadata']))
|
||||
->assertSessionHas('status', 'Depends on link type was updated.');
|
||||
|
||||
$this->assertDatabaseHas('ticket_link_types', [
|
||||
@@ -479,7 +483,7 @@ class ProjectTicketMetadataSettingsControllerTest extends TestCase
|
||||
|
||||
$this->actingAs($user)
|
||||
->delete(route('projects.settings.metadata.link-types.destroy', [$project, $ticketLinkType]))
|
||||
->assertRedirect(route('projects.settings.metadata', $project))
|
||||
->assertRedirect(route('projects.settings.index', ['project' => $project, 'section' => 'metadata']))
|
||||
->assertSessionHas('status', 'Depends on link type was removed.');
|
||||
|
||||
$this->assertDatabaseMissing('ticket_link_types', [
|
||||
|
||||
Reference in New Issue
Block a user