OneBot: Leantime 改造版源码(BOM/Univer 表格/AI 接管/品牌替换等)
This commit is contained in:
328
tests/Unit/app/Domain/Widgets/Services/DashboardServiceTest.php
Normal file
328
tests/Unit/app/Domain/Widgets/Services/DashboardServiceTest.php
Normal file
@@ -0,0 +1,328 @@
|
||||
<?php
|
||||
|
||||
namespace Unit\app\Domain\Widgets\Services;
|
||||
|
||||
use Leantime\Domain\Projects\Services\Projects as ProjectService;
|
||||
use Leantime\Domain\Reports\Services\Reports as ReportService;
|
||||
use Leantime\Domain\Setting\Services\Setting as SettingService;
|
||||
use Leantime\Domain\Tickets\Services\Tickets as TicketService;
|
||||
use Leantime\Domain\Users\Services\Users as UserService;
|
||||
use Leantime\Domain\Widgets\Services\Dashboard;
|
||||
use Leantime\Domain\Widgets\Services\Widgets;
|
||||
use Unit\TestCase;
|
||||
|
||||
/**
|
||||
* Unit tests for the Widgets Dashboard service that backs the Welcome and
|
||||
* "My To-Dos" dashboard widgets.
|
||||
*/
|
||||
class DashboardServiceTest extends TestCase
|
||||
{
|
||||
use \Codeception\Test\Feature\Stub;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
// Provide everything dtHelper() needs so getWelcomeWidgetData() can call
|
||||
// dtHelper()->userNow() without reaching for the Environment/Language.
|
||||
session(['usersettings.timezone' => 'UTC']);
|
||||
session(['usersettings.language' => 'en-US']);
|
||||
session(['usersettings.date_format' => 'Y-m-d']);
|
||||
session(['usersettings.time_format' => 'H:i']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a Dashboard service with the supplied (mocked) collaborators,
|
||||
* filling in empty stubs for any not provided.
|
||||
*/
|
||||
private function makeService(array $overrides = []): Dashboard
|
||||
{
|
||||
return new Dashboard(
|
||||
$overrides['tickets'] ?? $this->make(TicketService::class),
|
||||
$overrides['settings'] ?? $this->make(SettingService::class),
|
||||
$overrides['projects'] ?? $this->make(ProjectService::class),
|
||||
$overrides['users'] ?? $this->make(UserService::class),
|
||||
$overrides['reports'] ?? $this->make(ReportService::class),
|
||||
$overrides['widgets'] ?? $this->make(Widgets::class),
|
||||
);
|
||||
}
|
||||
|
||||
public function test_resolve_quick_add_due_date_keeps_existing_date(): void
|
||||
{
|
||||
$service = $this->makeService();
|
||||
|
||||
$result = $service->resolveQuickAddDueDate(['dateToFinish' => '2026-01-02', 'group' => 'thisWeek']);
|
||||
|
||||
$this->assertSame('2026-01-02', $result);
|
||||
}
|
||||
|
||||
public function test_resolve_quick_add_due_date_this_week_maps_to_next_friday(): void
|
||||
{
|
||||
$service = $this->makeService();
|
||||
|
||||
$result = $service->resolveQuickAddDueDate(['dateToFinish' => '', 'group' => 'thisWeek']);
|
||||
|
||||
$this->assertSame(date('Y-m-d', strtotime('next friday')), $result);
|
||||
}
|
||||
|
||||
public function test_resolve_quick_add_due_date_overdue_maps_to_today(): void
|
||||
{
|
||||
$service = $this->makeService();
|
||||
|
||||
$result = $service->resolveQuickAddDueDate(['group' => 'overdue']);
|
||||
|
||||
$this->assertSame(date('Y-m-d'), $result);
|
||||
}
|
||||
|
||||
public function test_resolve_quick_add_due_date_later_stays_empty(): void
|
||||
{
|
||||
$service = $this->makeService();
|
||||
|
||||
$this->assertSame('', $service->resolveQuickAddDueDate(['group' => 'later']));
|
||||
$this->assertSame('', $service->resolveQuickAddDueDate([]));
|
||||
}
|
||||
|
||||
public function test_map_group_to_fields_priority(): void
|
||||
{
|
||||
$service = $this->makeService();
|
||||
|
||||
$this->assertSame(['priority' => 2], $service->mapGroupToFields('priority', '2'));
|
||||
$this->assertSame(['priority' => ''], $service->mapGroupToFields('priority', '999'));
|
||||
$this->assertSame([], $service->mapGroupToFields('priority', '7'));
|
||||
}
|
||||
|
||||
public function test_map_group_to_fields_project(): void
|
||||
{
|
||||
$service = $this->makeService();
|
||||
|
||||
$this->assertSame(['projectId' => 5], $service->mapGroupToFields('project', '5'));
|
||||
$this->assertSame([], $service->mapGroupToFields('project', '0'));
|
||||
}
|
||||
|
||||
public function test_map_group_to_fields_time(): void
|
||||
{
|
||||
$service = $this->makeService();
|
||||
|
||||
$this->assertSame(
|
||||
['dateToFinish' => date('Y-m-d', strtotime('yesterday'))],
|
||||
$service->mapGroupToFields('time', 'overdue')
|
||||
);
|
||||
$this->assertSame(['dateToFinish' => ''], $service->mapGroupToFields('time', 'later'));
|
||||
$this->assertSame([], $service->mapGroupToFields('time', 'bogus'));
|
||||
}
|
||||
|
||||
public function test_map_group_to_fields_unknown_group_by_returns_empty(): void
|
||||
{
|
||||
$service = $this->makeService();
|
||||
|
||||
$this->assertSame([], $service->mapGroupToFields('unknown', 'whatever'));
|
||||
}
|
||||
|
||||
public function test_has_more_tickets_preserves_full_page_semantics(): void
|
||||
{
|
||||
$service = $this->makeService();
|
||||
|
||||
// Two groups. countNested over the whole collection counts each group node
|
||||
// plus its nested tickets: group1 = 1 + 2 = 3, group2 = 1 + 1 = 2, total = 5.
|
||||
// The legacy loop re-counts the whole collection once per group, so the
|
||||
// returned total is 5 * 2 = 10. This quirk is preserved deliberately.
|
||||
$groups = [
|
||||
['tickets' => [['id' => 1], ['id' => 2]]],
|
||||
['tickets' => [['id' => 3]]],
|
||||
];
|
||||
|
||||
$this->assertTrue($service->hasMoreTickets($groups, 10));
|
||||
$this->assertTrue($service->hasMoreTickets($groups, 9));
|
||||
$this->assertFalse($service->hasMoreTickets($groups, 11));
|
||||
$this->assertFalse($service->hasMoreTickets([], 1));
|
||||
}
|
||||
|
||||
public function test_add_todo_resolves_due_date_then_delegates(): void
|
||||
{
|
||||
$captured = null;
|
||||
$tickets = $this->make(TicketService::class, [
|
||||
'quickAddTicket' => function ($params) use (&$captured) {
|
||||
$captured = $params;
|
||||
|
||||
return ['status' => 'success'];
|
||||
},
|
||||
]);
|
||||
|
||||
$service = $this->makeService(['tickets' => $tickets]);
|
||||
|
||||
$result = $service->addTodo(['quickadd' => '1', 'dateToFinish' => '', 'group' => 'overdue']);
|
||||
|
||||
$this->assertSame(['status' => 'success'], $result);
|
||||
$this->assertSame(date('Y-m-d'), $captured['dateToFinish']);
|
||||
}
|
||||
|
||||
public function test_toggle_task_collapse_flips_state_and_persists(): void
|
||||
{
|
||||
$saved = [];
|
||||
$settings = $this->make(SettingService::class, [
|
||||
'getSetting' => fn () => 'open',
|
||||
'saveSetting' => function ($key, $value) use (&$saved) {
|
||||
$saved[$key] = $value;
|
||||
|
||||
return true;
|
||||
},
|
||||
]);
|
||||
|
||||
$service = $this->makeService(['settings' => $settings]);
|
||||
|
||||
$newState = $service->toggleTaskCollapse(7, '42');
|
||||
|
||||
$this->assertSame('closed', $newState);
|
||||
$this->assertSame('closed', $saved['user.7.taskCollapsed.42']);
|
||||
}
|
||||
|
||||
public function test_save_todo_sorting_normalizes_order_and_persists(): void
|
||||
{
|
||||
$savedValue = null;
|
||||
$settings = $this->make(SettingService::class, [
|
||||
'saveSetting' => function ($key, $value) use (&$savedValue) {
|
||||
$savedValue = $value;
|
||||
|
||||
return true;
|
||||
},
|
||||
]);
|
||||
// No dependencies / patches expected when there are no parents.
|
||||
$tickets = $this->make(TicketService::class, [
|
||||
'patch' => fn () => true,
|
||||
]);
|
||||
|
||||
$service = $this->makeService(['settings' => $settings, 'tickets' => $tickets]);
|
||||
|
||||
$rawItems = [
|
||||
json_encode(['id' => 1, 'order' => 0]),
|
||||
json_encode(['id' => 2, 'order' => 5]),
|
||||
];
|
||||
|
||||
$result = $service->saveTodoSorting(99, $rawItems, [], 'time');
|
||||
|
||||
$this->assertTrue($result['sorted']);
|
||||
$this->assertSame(0, $result['successCount']);
|
||||
$this->assertSame(0, $result['errorCount']);
|
||||
|
||||
$persisted = json_decode($savedValue, true);
|
||||
$this->assertSame(10, $persisted[0]['order']);
|
||||
$this->assertSame(15, $persisted[1]['order']);
|
||||
}
|
||||
|
||||
public function test_save_todo_sorting_returns_not_sorted_for_non_array_payload(): void
|
||||
{
|
||||
$service = $this->makeService();
|
||||
|
||||
$result = $service->saveTodoSorting(99, 'not-an-array', [], 'time');
|
||||
|
||||
$this->assertFalse($result['sorted']);
|
||||
$this->assertSame(0, $result['successCount']);
|
||||
$this->assertSame(0, $result['errorCount']);
|
||||
}
|
||||
|
||||
public function test_update_ticket_dependencies_sets_and_clears_parents(): void
|
||||
{
|
||||
$patches = [];
|
||||
$tickets = $this->make(TicketService::class, [
|
||||
'patch' => function ($id, $fields) use (&$patches) {
|
||||
$patches[$id] = $fields;
|
||||
|
||||
return true;
|
||||
},
|
||||
]);
|
||||
|
||||
$service = $this->makeService(['tickets' => $tickets]);
|
||||
|
||||
$service->updateTicketDependencies([
|
||||
['id' => 1, 'parentId' => 5, 'parentType' => 'ticket'],
|
||||
['id' => 2, 'parentId' => null, 'parentType' => null],
|
||||
['id' => 3, 'parentId' => 3, 'parentType' => 'ticket'], // self-reference skipped
|
||||
]);
|
||||
|
||||
$this->assertSame(['dependingTicketId' => 5], $patches[1]);
|
||||
$this->assertSame(['dependingTicketId' => '', 'milestoneid' => ''], $patches[2]);
|
||||
$this->assertArrayNotHasKey(3, $patches);
|
||||
}
|
||||
|
||||
public function test_get_welcome_widget_data_aggregates_counts(): void
|
||||
{
|
||||
session(['userdata' => ['id' => 4]]);
|
||||
session(['usersettings.timezone' => 'UTC']);
|
||||
|
||||
$tickets = $this->make(TicketService::class, [
|
||||
'simpleTicketCounter' => fn () => 12,
|
||||
'getRecentlyCompletedTicketsByUser' => fn () => [['id' => 1], ['id' => 2]],
|
||||
'goalsRelatedToWork' => fn () => [['id' => 9]],
|
||||
'getScheduledTasks' => fn () => [
|
||||
'totalTasks' => [['id' => 1], ['id' => 2], ['id' => 3]],
|
||||
'doneTasks' => [['id' => 1]],
|
||||
],
|
||||
]);
|
||||
$projects = $this->make(ProjectService::class, [
|
||||
'getProjectsAssignedToUser' => fn () => [['id' => 1], ['id' => 2]],
|
||||
]);
|
||||
$users = $this->make(UserService::class, [
|
||||
'getUser' => fn () => ['id' => 4, 'username' => 'tester'],
|
||||
]);
|
||||
$widgets = $this->make(Widgets::class, [
|
||||
'getNewWidgets' => fn () => ['todos' => true],
|
||||
]);
|
||||
|
||||
$service = $this->makeService([
|
||||
'tickets' => $tickets,
|
||||
'projects' => $projects,
|
||||
'users' => $users,
|
||||
'widgets' => $widgets,
|
||||
]);
|
||||
|
||||
$data = $service->getWelcomeWidgetData(4);
|
||||
|
||||
$this->assertSame(12, $data['totalTickets']);
|
||||
$this->assertSame(2, $data['closedTicketsCount']);
|
||||
$this->assertSame(1, $data['ticketsInGoals']);
|
||||
$this->assertSame(3, $data['totalTodayCount']);
|
||||
$this->assertSame(1, $data['doneTodayCount']);
|
||||
$this->assertSame(2, $data['projectCount']);
|
||||
$this->assertTrue($data['showSettingsIndicator']);
|
||||
$this->assertSame(['id' => 4, 'username' => 'tester'], $data['currentUser']);
|
||||
}
|
||||
|
||||
public function test_get_welcome_widget_data_handles_non_array_results(): void
|
||||
{
|
||||
session(['userdata' => ['id' => 4]]);
|
||||
session(['usersettings.timezone' => 'UTC']);
|
||||
|
||||
$tickets = $this->make(TicketService::class, [
|
||||
'simpleTicketCounter' => fn () => 0,
|
||||
'getRecentlyCompletedTicketsByUser' => fn () => [],
|
||||
'goalsRelatedToWork' => fn () => false,
|
||||
'getScheduledTasks' => fn () => [],
|
||||
]);
|
||||
$projects = $this->make(ProjectService::class, [
|
||||
'getProjectsAssignedToUser' => fn () => [],
|
||||
]);
|
||||
$users = $this->make(UserService::class, [
|
||||
'getUser' => fn () => ['id' => 4],
|
||||
]);
|
||||
$widgets = $this->make(Widgets::class, [
|
||||
'getNewWidgets' => fn () => [],
|
||||
]);
|
||||
|
||||
$service = $this->makeService([
|
||||
'tickets' => $tickets,
|
||||
'projects' => $projects,
|
||||
'users' => $users,
|
||||
'widgets' => $widgets,
|
||||
]);
|
||||
|
||||
$data = $service->getWelcomeWidgetData(4);
|
||||
|
||||
$this->assertSame(0, $data['closedTicketsCount']);
|
||||
$this->assertSame(0, $data['ticketsInGoals']);
|
||||
$this->assertSame(0, $data['totalTodayCount']);
|
||||
$this->assertSame(0, $data['doneTodayCount']);
|
||||
$this->assertSame([], $data['allProjects']);
|
||||
$this->assertSame(0, $data['projectCount']);
|
||||
$this->assertFalse($data['showSettingsIndicator']);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
<?php
|
||||
|
||||
namespace Unit\app\Domain\Widgets\Services;
|
||||
|
||||
use Leantime\Domain\Projects\Services\Projects as ProjectService;
|
||||
use Leantime\Domain\Reports\Services\Reports as ReportService;
|
||||
use Leantime\Domain\Setting\Repositories\Setting;
|
||||
use Leantime\Domain\Widgets\Services\Widgets;
|
||||
use Unit\TestCase;
|
||||
|
||||
/**
|
||||
* Unit tests for the Widgets service aggregation extracted from the
|
||||
* Widgets/MyProjects HxController.
|
||||
*/
|
||||
class WidgetsServiceTest extends TestCase
|
||||
{
|
||||
use \Codeception\Test\Feature\Stub;
|
||||
|
||||
private function makeService(ProjectService $projectService, ReportService $reportService): Widgets
|
||||
{
|
||||
return new Widgets($this->make(Setting::class), $projectService, $reportService);
|
||||
}
|
||||
|
||||
public function test_my_projects_widget_data_enriches_each_project(): void
|
||||
{
|
||||
$projectService = $this->make(ProjectService::class, [
|
||||
'getProjectsAssignedToUser' => fn () => [
|
||||
['id' => 1, 'clientId' => 10, 'clientName' => 'Acme'],
|
||||
['id' => 2, 'clientId' => 20, 'clientName' => 'Globex'],
|
||||
],
|
||||
'getProjectProgress' => fn ($id) => ['percent' => 42, 'projectId' => $id],
|
||||
]);
|
||||
$reportService = $this->make(ReportService::class, [
|
||||
'getRealtimeReport' => fn ($id, $sprint) => ['report' => true, 'projectId' => $id],
|
||||
]);
|
||||
|
||||
$result = $this->makeService($projectService, $reportService)->getMyProjectsWidgetData(5);
|
||||
|
||||
$this->assertCount(2, $result['projects']);
|
||||
$this->assertSame(42, $result['projects'][0]['progress']['percent']);
|
||||
$this->assertSame(1, $result['projects'][0]['report']['projectId']);
|
||||
$this->assertSame([10 => 'Acme', 20 => 'Globex'], $result['clients']);
|
||||
}
|
||||
|
||||
public function test_my_projects_widget_data_filters_by_client(): void
|
||||
{
|
||||
$projectService = $this->make(ProjectService::class, [
|
||||
'getProjectsAssignedToUser' => fn () => [
|
||||
['id' => 1, 'clientId' => 10, 'clientName' => 'Acme'],
|
||||
['id' => 2, 'clientId' => 20, 'clientName' => 'Globex'],
|
||||
],
|
||||
'getProjectProgress' => fn ($id) => ['percent' => 0],
|
||||
]);
|
||||
$reportService = $this->make(ReportService::class, [
|
||||
'getRealtimeReport' => fn ($id, $sprint) => [],
|
||||
]);
|
||||
|
||||
$result = $this->makeService($projectService, $reportService)->getMyProjectsWidgetData(5, '20');
|
||||
|
||||
// Both clients are still mapped, but only the matching project is enriched/returned.
|
||||
$this->assertCount(1, $result['projects']);
|
||||
$this->assertSame(2, $result['projects'][0]['id']);
|
||||
$this->assertArrayHasKey(10, $result['clients']);
|
||||
$this->assertArrayHasKey(20, $result['clients']);
|
||||
}
|
||||
|
||||
public function test_my_projects_widget_data_handles_no_projects(): void
|
||||
{
|
||||
$projectService = $this->make(ProjectService::class, [
|
||||
'getProjectsAssignedToUser' => fn () => [],
|
||||
]);
|
||||
$reportService = $this->make(ReportService::class);
|
||||
|
||||
$result = $this->makeService($projectService, $reportService)->getMyProjectsWidgetData(5);
|
||||
|
||||
$this->assertSame([], $result['projects']);
|
||||
$this->assertSame([], $result['clients']);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user