Signed-off-by: Georg Ehrke <developer@georgehrke.com>tags/v20.0.0beta1
@@ -15,6 +15,7 @@ return array( | |||
'OCA\\UserStatus\\Controller\\PredefinedStatusController' => $baseDir . '/../lib/Controller/PredefinedStatusController.php', | |||
'OCA\\UserStatus\\Controller\\StatusesController' => $baseDir . '/../lib/Controller/StatusesController.php', | |||
'OCA\\UserStatus\\Controller\\UserStatusController' => $baseDir . '/../lib/Controller/UserStatusController.php', | |||
'OCA\\UserStatus\\Dashboard\\UserStatusWidget' => $baseDir . '/../lib/Dashboard/UserStatusWidget.php', | |||
'OCA\\UserStatus\\Db\\UserStatus' => $baseDir . '/../lib/Db/UserStatus.php', | |||
'OCA\\UserStatus\\Db\\UserStatusMapper' => $baseDir . '/../lib/Db/UserStatusMapper.php', | |||
'OCA\\UserStatus\\Exception\\InvalidClearAtException' => $baseDir . '/../lib/Exception/InvalidClearAtException.php', |
@@ -30,6 +30,7 @@ class ComposerStaticInitUserStatus | |||
'OCA\\UserStatus\\Controller\\PredefinedStatusController' => __DIR__ . '/..' . '/../lib/Controller/PredefinedStatusController.php', | |||
'OCA\\UserStatus\\Controller\\StatusesController' => __DIR__ . '/..' . '/../lib/Controller/StatusesController.php', | |||
'OCA\\UserStatus\\Controller\\UserStatusController' => __DIR__ . '/..' . '/../lib/Controller/UserStatusController.php', | |||
'OCA\\UserStatus\\Dashboard\\UserStatusWidget' => __DIR__ . '/..' . '/../lib/Dashboard/UserStatusWidget.php', | |||
'OCA\\UserStatus\\Db\\UserStatus' => __DIR__ . '/..' . '/../lib/Db/UserStatus.php', | |||
'OCA\\UserStatus\\Db\\UserStatusMapper' => __DIR__ . '/..' . '/../lib/Db/UserStatusMapper.php', | |||
'OCA\\UserStatus\\Exception\\InvalidClearAtException' => __DIR__ . '/..' . '/../lib/Exception/InvalidClearAtException.php', |
@@ -20,6 +20,10 @@ | |||
* | |||
*/ | |||
.icon-user-status { | |||
@include icon-color('app', 'user_status', $color-black, 1); | |||
} | |||
.icon-user-status-away { | |||
@include icon-color('user-status-away', 'user_status', '#F4A331', 2); | |||
} |
@@ -30,6 +30,7 @@ use OCA\UserStatus\Connector\UserStatusProvider; | |||
use OCA\UserStatus\Listener\BeforeTemplateRenderedListener; | |||
use OCA\UserStatus\Listener\UserDeletedListener; | |||
use OCA\UserStatus\Listener\UserLiveStatusListener; | |||
use OCA\UserStatus\Dashboard\UserStatusWidget; | |||
use OCP\AppFramework\App; | |||
use OCP\AppFramework\Bootstrap\IBootContext; | |||
use OCP\AppFramework\Bootstrap\IBootstrap; | |||
@@ -69,6 +70,9 @@ class Application extends App implements IBootstrap { | |||
$context->registerEventListener(UserDeletedEvent::class, UserDeletedListener::class); | |||
$context->registerEventListener(UserLiveStatusEvent::class, UserLiveStatusListener::class); | |||
$context->registerEventListener(BeforeTemplateRenderedEvent::class, BeforeTemplateRenderedListener::class); | |||
// Register the Dashboard panel | |||
$context->registerDashboardWidget(UserStatusWidget::class); | |||
} | |||
public function boot(IBootContext $context): void { |
@@ -0,0 +1,156 @@ | |||
<?php | |||
declare(strict_types=1); | |||
/** | |||
* @copyright Copyright (c) 2020, Georg Ehrke | |||
* | |||
* @author Georg Ehrke <oc.list@georgehrke.com> | |||
* | |||
* @license AGPL-3.0 | |||
* | |||
* This code is free software: you can redistribute it and/or modify | |||
* it under the terms of the GNU Affero General Public License, version 3, | |||
* as published by the Free Software Foundation. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
* GNU Affero General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Affero General Public License, version 3, | |||
* along with this program. If not, see <http://www.gnu.org/licenses/> | |||
* | |||
*/ | |||
namespace OCA\UserStatus\Dashboard; | |||
use OCA\UserStatus\AppInfo\Application; | |||
use OCA\UserStatus\Db\UserStatus; | |||
use OCA\UserStatus\Service\StatusService; | |||
use OCP\Dashboard\IWidget; | |||
use OCP\IInitialStateService; | |||
use OCP\IL10N; | |||
use OCP\IUserManager; | |||
use OCP\IUserSession; | |||
/** | |||
* Class UserStatusWidget | |||
* | |||
* @package OCA\UserStatus | |||
*/ | |||
class UserStatusWidget implements IWidget { | |||
/** @var IL10N */ | |||
private $l10n; | |||
/** @var IInitialStateService */ | |||
private $initialStateService; | |||
/** @var IUserManager */ | |||
private $userManager; | |||
/** @var IUserSession */ | |||
private $userSession; | |||
/** @var StatusService */ | |||
private $service; | |||
/** | |||
* UserStatusWidget constructor | |||
* | |||
* @param IL10N $l10n | |||
* @param IInitialStateService $initialStateService | |||
* @param IUserManager $userManager | |||
* @param IUserSession $userSession | |||
* @param StatusService $service | |||
*/ | |||
public function __construct(IL10N $l10n, | |||
IInitialStateService $initialStateService, | |||
IUserManager $userManager, | |||
IUserSession $userSession, | |||
StatusService $service) { | |||
$this->l10n = $l10n; | |||
$this->initialStateService = $initialStateService; | |||
$this->userManager = $userManager; | |||
$this->userSession = $userSession; | |||
$this->service = $service; | |||
} | |||
/** | |||
* @inheritDoc | |||
*/ | |||
public function getId(): string { | |||
return Application::APP_ID; | |||
} | |||
/** | |||
* @inheritDoc | |||
*/ | |||
public function getTitle(): string { | |||
return $this->l10n->t('Recent statuses'); | |||
} | |||
/** | |||
* @inheritDoc | |||
*/ | |||
public function getOrder(): int { | |||
return 5; | |||
} | |||
/** | |||
* @inheritDoc | |||
*/ | |||
public function getIconClass(): string { | |||
return 'icon-user-status'; | |||
} | |||
/** | |||
* @inheritDoc | |||
*/ | |||
public function getUrl(): ?string { | |||
return null; | |||
} | |||
/** | |||
* @inheritDoc | |||
*/ | |||
public function load(): void { | |||
\OCP\Util::addScript(Application::APP_ID, 'dashboard'); | |||
$currentUser = $this->userSession->getUser(); | |||
if ($currentUser === null) { | |||
$this->initialStateService->provideInitialState(Application::APP_ID, 'dashboard_data', []); | |||
return; | |||
} | |||
$currentUserId = $currentUser->getUID(); | |||
// Fetch status updates and filter current user | |||
$recentStatusUpdates = array_slice( | |||
array_filter( | |||
$this->service->findAllRecentStatusChanges(8, 0), | |||
static function (UserStatus $status) use ($currentUserId): bool { | |||
return $status->getUserId() !== $currentUserId; | |||
} | |||
), | |||
0, | |||
7 | |||
); | |||
$this->initialStateService->provideInitialState(Application::APP_ID, 'dashboard_data', array_map(function (UserStatus $status): array { | |||
$user = $this->userManager->get($status->getUserId()); | |||
$displayName = $status->getUserId(); | |||
if ($user !== null) { | |||
$displayName = $user->getDisplayName(); | |||
} | |||
return [ | |||
'userId' => $status->getUserId(), | |||
'displayName' => $displayName, | |||
'status' => $status->getStatus() === 'invisible' ? 'offline' : $status->getStatus(), | |||
'icon' => $status->getCustomIcon(), | |||
'message' => $status->getCustomMessage(), | |||
'timestamp' => $status->getStatusTimestamp(), | |||
]; | |||
}, $recentStatusUpdates)); | |||
} | |||
} |
@@ -69,6 +69,33 @@ class UserStatusMapper extends QBMapper { | |||
return $this->findEntities($qb); | |||
} | |||
/** | |||
* @param int|null $limit | |||
* @param int|null $offset | |||
* @return array | |||
*/ | |||
public function findAllRecent(?int $limit = null, ?int $offset = null): array { | |||
$qb = $this->db->getQueryBuilder(); | |||
$qb | |||
->select('*') | |||
->from($this->tableName) | |||
->orderBy('status_timestamp', 'DESC') | |||
->where($qb->expr()->notIn('status', $qb->createNamedParameter(['online', 'away'], IQueryBuilder::PARAM_STR_ARRAY))) | |||
->orWhere($qb->expr()->isNotNull('message_id')) | |||
->orWhere($qb->expr()->isNotNull('custom_icon')) | |||
->orWhere($qb->expr()->isNotNull('custom_message')); | |||
if ($limit !== null) { | |||
$qb->setMaxResults($limit); | |||
} | |||
if ($offset !== null) { | |||
$qb->setFirstResult($offset); | |||
} | |||
return $this->findEntities($qb); | |||
} | |||
/** | |||
* @param string $userId | |||
* @return UserStatus |
@@ -95,6 +95,17 @@ class StatusService { | |||
}, $this->mapper->findAll($limit, $offset)); | |||
} | |||
/** | |||
* @param int|null $limit | |||
* @param int|null $offset | |||
* @return array | |||
*/ | |||
public function findAllRecentStatusChanges(?int $limit = null, ?int $offset = null): array { | |||
return array_map(function ($status) { | |||
return $this->processStatus($status); | |||
}, $this->mapper->findAllRecent($limit, $offset)); | |||
} | |||
/** | |||
* @param string $userId | |||
* @return UserStatus |
@@ -0,0 +1,48 @@ | |||
/** | |||
* @copyright Copyright (c) 2020 Georg Ehrke | |||
* | |||
* @author Georg Ehrke <oc.list@georgehrke.com> | |||
* | |||
* @license GNU AGPL version 3 or any later version | |||
* | |||
* This program is free software: you can redistribute it and/or modify | |||
* it under the terms of the GNU Affero General Public License as | |||
* published by the Free Software Foundation, either version 3 of the | |||
* License, or (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
* GNU Affero General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Affero General Public License | |||
* along with this program. If not, see <http://www.gnu.org/licenses/>. | |||
* | |||
*/ | |||
import Vue from 'vue' | |||
import { generateFilePath } from '@nextcloud/router' | |||
import { getRequestToken } from '@nextcloud/auth' | |||
import { translate, translatePlural } from '@nextcloud/l10n' | |||
import Dashboard from './views/Dashboard' | |||
// eslint-disable-next-line | |||
__webpack_nonce__ = btoa(getRequestToken()) | |||
// eslint-disable-next-line | |||
__webpack_public_path__ = generateFilePath('user_status', '', 'js/') | |||
Vue.prototype.t = translate | |||
Vue.prototype.n = translatePlural | |||
Vue.prototype.OC = OC | |||
Vue.prototype.OCA = OCA | |||
document.addEventListener('DOMContentLoaded', function() { | |||
OCA.Dashboard.register('user_status', (el) => { | |||
const View = Vue.extend(Dashboard) | |||
new View({ | |||
propsData: {}, | |||
}).$mount(el) | |||
}) | |||
}) |
@@ -1,3 +1,24 @@ | |||
/** | |||
* @copyright Copyright (c) 2020 Georg Ehrke | |||
* | |||
* @author Georg Ehrke <oc.list@georgehrke.com> | |||
* | |||
* @license GNU AGPL version 3 or any later version | |||
* | |||
* This program is free software: you can redistribute it and/or modify | |||
* it under the terms of the GNU Affero General Public License as | |||
* published by the Free Software Foundation, either version 3 of the | |||
* License, or (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
* GNU Affero General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Affero General Public License | |||
* along with this program. If not, see <http://www.gnu.org/licenses/>. | |||
* | |||
*/ | |||
import Vue from 'vue' | |||
import { getRequestToken } from '@nextcloud/auth' | |||
import App from './App' |
@@ -0,0 +1,100 @@ | |||
<!-- | |||
- @copyright Copyright (c) 2020 Georg Ehrke <oc.list@georgehrke.com> | |||
- @author Georg Ehrke <oc.list@georgehrke.com> | |||
- | |||
- @license GNU AGPL version 3 or any later version | |||
- | |||
- This program is free software: you can redistribute it and/or modify | |||
- it under the terms of the GNU Affero General Public License as | |||
- published by the Free Software Foundation, either version 3 of the | |||
- License, or (at your option) any later version. | |||
- | |||
- This program is distributed in the hope that it will be useful, | |||
- but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
- GNU Affero General Public License for more details. | |||
- | |||
- You should have received a copy of the GNU Affero General Public License | |||
- along with this program. If not, see <http://www.gnu.org/licenses/>. | |||
- | |||
--> | |||
<template> | |||
<DashboardWidget | |||
id="user-status_panel" | |||
:items="items" | |||
:loading="loading"> | |||
<template v-slot:empty-content> | |||
<EmptyContent | |||
id="user_status-widget-empty-content" | |||
icon="icon-user-status"> | |||
{{ t('user_status', 'No recent status changes') }} | |||
</EmptyContent> | |||
</template> | |||
</DashboardWidget> | |||
</template> | |||
<script> | |||
import { DashboardWidget } from '@nextcloud/vue-dashboard' | |||
import EmptyContent from '@nextcloud/vue/dist/Components/EmptyContent' | |||
import { loadState } from '@nextcloud/initial-state' | |||
import moment from '@nextcloud/moment' | |||
export default { | |||
name: 'Dashboard', | |||
components: { | |||
DashboardWidget, | |||
EmptyContent, | |||
}, | |||
data() { | |||
return { | |||
statuses: [], | |||
loading: true, | |||
} | |||
}, | |||
computed: { | |||
items() { | |||
return this.statuses.map((item) => { | |||
const icon = item.icon || '' | |||
const message = item.message || '' | |||
const status = `${icon} ${message}` | |||
let subText | |||
if (item.icon === null && item.message === null && item.timestamp === null) { | |||
subText = '' | |||
} else if (item.icon === null && item.message === null && item.timestamp !== null) { | |||
subText = moment(item.timestamp, 'X').fromNow() | |||
} else if (item.timestamp !== null) { | |||
subText = this.t('user_status', '{status}, {timestamp}', { | |||
status, | |||
timestamp: moment(item.timestamp, 'X').fromNow(), | |||
}) | |||
} else { | |||
subText = status | |||
} | |||
return { | |||
mainText: item.displayName, | |||
subText, | |||
avatarUsername: item.userId, | |||
} | |||
}) | |||
}, | |||
}, | |||
mounted() { | |||
try { | |||
this.statuses = loadState('user_status', 'dashboard_data') | |||
this.loading = false | |||
} catch (e) { | |||
console.error(e) | |||
} | |||
}, | |||
} | |||
</script> | |||
<style lang="scss"> | |||
#user_status-widget-empty-content { | |||
text-align: center; | |||
margin-top: 5vh; | |||
} | |||
</style> |
@@ -0,0 +1,261 @@ | |||
<?php | |||
declare(strict_types=1); | |||
/** | |||
* @copyright Copyright (c) 2020, Georg Ehrke | |||
* | |||
* @author Georg Ehrke <oc.list@georgehrke.com> | |||
* | |||
* @license AGPL-3.0 | |||
* | |||
* This code is free software: you can redistribute it and/or modify | |||
* it under the terms of the GNU Affero General Public License, version 3, | |||
* as published by the Free Software Foundation. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
* GNU Affero General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Affero General Public License, version 3, | |||
* along with this program. If not, see <http://www.gnu.org/licenses/> | |||
* | |||
*/ | |||
namespace OCA\UserStatus\Tests\Dashboard; | |||
use OCA\UserStatus\Dashboard\UserStatusWidget; | |||
use OCA\UserStatus\Db\UserStatus; | |||
use OCA\UserStatus\Service\StatusService; | |||
use OCP\IInitialStateService; | |||
use OCP\IL10N; | |||
use OCP\IUser; | |||
use OCP\IUserManager; | |||
use OCP\IUserSession; | |||
use Test\TestCase; | |||
class UserStatusWidgetTest extends TestCase { | |||
/** @var IL10N|\PHPUnit\Framework\MockObject\MockObject */ | |||
private $l10n; | |||
/** @var IInitialStateService|\PHPUnit\Framework\MockObject\MockObject */ | |||
private $initialState; | |||
/** @var IUserManager|\PHPUnit\Framework\MockObject\MockObject */ | |||
private $userManager; | |||
/** @var IUserSession|\PHPUnit\Framework\MockObject\MockObject */ | |||
private $userSession; | |||
/** @var StatusService|\PHPUnit\Framework\MockObject\MockObject */ | |||
private $service; | |||
/** @var UserStatusWidget */ | |||
private $widget; | |||
protected function setUp(): void { | |||
parent::setUp(); | |||
$this->l10n = $this->createMock(IL10N::class); | |||
$this->initialState = $this->createMock(IInitialStateService::class); | |||
$this->userManager = $this->createMock(IUserManager::class); | |||
$this->userSession = $this->createMock(IUserSession::class); | |||
$this->service = $this->createMock(StatusService::class); | |||
$this->widget = new UserStatusWidget($this->l10n, $this->initialState, $this->userManager, $this->userSession, $this->service); | |||
} | |||
public function testGetId(): void { | |||
$this->assertEquals('user_status', $this->widget->getId()); | |||
} | |||
public function testGetTitle(): void { | |||
$this->l10n->expects($this->exactly(1)) | |||
->method('t') | |||
->willReturnArgument(0); | |||
$this->assertEquals('Recent statuses', $this->widget->getTitle()); | |||
} | |||
public function testGetOrder(): void { | |||
$this->assertEquals(5, $this->widget->getOrder()); | |||
} | |||
public function testGetIconClass(): void { | |||
$this->assertEquals('icon-user-status', $this->widget->getIconClass()); | |||
} | |||
public function testGetUrl(): void { | |||
$this->assertNull($this->widget->getUrl()); | |||
} | |||
public function testLoadNoUserSession(): void { | |||
$this->userSession->expects($this->once()) | |||
->method('getUser') | |||
->willReturn(null); | |||
$this->initialState->expects($this->once()) | |||
->method('provideInitialState') | |||
->with('user_status', 'dashboard_data', []); | |||
$this->service->expects($this->never()) | |||
->method('findAllRecentStatusChanges'); | |||
$this->widget->load(); | |||
} | |||
public function testLoadWithCurrentUser(): void { | |||
$user = $this->createMock(IUser::class); | |||
$user->method('getUid')->willReturn('john.doe'); | |||
$this->userSession->expects($this->once()) | |||
->method('getUser') | |||
->willReturn($user); | |||
$user1 = $this->createMock(IUser::class); | |||
$user1->method('getDisplayName')->willReturn('User No. 1'); | |||
$this->userManager | |||
->method('get') | |||
->willReturnMap([ | |||
['user_1', $user1], | |||
['user_2', null], | |||
['user_3', null], | |||
['user_4', null], | |||
['user_5', null], | |||
['user_6', null], | |||
['user_7', null], | |||
]); | |||
$userStatuses = [ | |||
UserStatus::fromParams([ | |||
'userId' => 'user_1', | |||
'status' => 'online', | |||
'customIcon' => '💻', | |||
'customMessage' => 'Working', | |||
'statusTimestamp' => 5000, | |||
]), | |||
UserStatus::fromParams([ | |||
'userId' => 'user_2', | |||
'status' => 'away', | |||
'customIcon' => '☕️', | |||
'customMessage' => 'Office Hangout', | |||
'statusTimestamp' => 6000, | |||
]), | |||
UserStatus::fromParams([ | |||
'userId' => 'user_3', | |||
'status' => 'dnd', | |||
'customIcon' => null, | |||
'customMessage' => null, | |||
'statusTimestamp' => 7000, | |||
]), | |||
UserStatus::fromParams([ | |||
'userId' => 'john.doe', | |||
'status' => 'away', | |||
'customIcon' => '☕️', | |||
'customMessage' => 'Office Hangout', | |||
'statusTimestamp' => 90000, | |||
]), | |||
UserStatus::fromParams([ | |||
'userId' => 'user_4', | |||
'status' => 'dnd', | |||
'customIcon' => null, | |||
'customMessage' => null, | |||
'statusTimestamp' => 7000, | |||
]), | |||
UserStatus::fromParams([ | |||
'userId' => 'user_5', | |||
'status' => 'invisible', | |||
'customIcon' => '🏝', | |||
'customMessage' => 'On vacation', | |||
'statusTimestamp' => 7000, | |||
]), | |||
UserStatus::fromParams([ | |||
'userId' => 'user_6', | |||
'status' => 'offline', | |||
'customIcon' => null, | |||
'customMessage' => null, | |||
'statusTimestamp' => 7000, | |||
]), | |||
UserStatus::fromParams([ | |||
'userId' => 'user_7', | |||
'status' => 'invisible', | |||
'customIcon' => null, | |||
'customMessage' => null, | |||
'statusTimestamp' => 7000, | |||
]), | |||
]; | |||
$this->service->expects($this->once()) | |||
->method('findAllRecentStatusChanges') | |||
->with(8, 0) | |||
->willReturn($userStatuses); | |||
$this->initialState->expects($this->once()) | |||
->method('provideInitialState') | |||
->with('user_status', 'dashboard_data', $this->callback(function ($data): bool { | |||
$this->assertEquals([ | |||
[ | |||
'userId' => 'user_1', | |||
'displayName' => 'User No. 1', | |||
'status' => 'online', | |||
'icon' => '💻', | |||
'message' => 'Working', | |||
'timestamp' => 5000, | |||
], | |||
[ | |||
'userId' => 'user_2', | |||
'displayName' => 'user_2', | |||
'status' => 'away', | |||
'icon' => '☕️', | |||
'message' => 'Office Hangout', | |||
'timestamp' => 6000, | |||
], | |||
[ | |||
'userId' => 'user_3', | |||
'displayName' => 'user_3', | |||
'status' => 'dnd', | |||
'icon' => null, | |||
'message' => null, | |||
'timestamp' => 7000, | |||
], | |||
[ | |||
'userId' => 'user_4', | |||
'displayName' => 'user_4', | |||
'status' => 'dnd', | |||
'icon' => null, | |||
'message' => null, | |||
'timestamp' => 7000, | |||
], | |||
[ | |||
'userId' => 'user_5', | |||
'displayName' => 'user_5', | |||
'status' => 'offline', | |||
'icon' => '🏝', | |||
'message' => 'On vacation', | |||
'timestamp' => 7000, | |||
], | |||
[ | |||
'userId' => 'user_6', | |||
'displayName' => 'user_6', | |||
'status' => 'offline', | |||
'icon' => null, | |||
'message' => null, | |||
'timestamp' => 7000, | |||
], | |||
[ | |||
'userId' => 'user_7', | |||
'displayName' => 'user_7', | |||
'status' => 'offline', | |||
'icon' => null, | |||
'message' => null, | |||
'timestamp' => 7000, | |||
], | |||
], $data); | |||
return true; | |||
})); | |||
$this->widget->load(); | |||
} | |||
} |
@@ -65,6 +65,15 @@ class UserStatusMapperTest extends TestCase { | |||
$this->assertEquals('user2', $offsetResults[0]->getUserId()); | |||
} | |||
public function testFindAllRecent(): void { | |||
$this->insertSampleStatuses(); | |||
$allResults = $this->mapper->findAllRecent(2, 0); | |||
$this->assertCount(2, $allResults); | |||
$this->assertEquals('user1', $allResults[0]->getUserId()); | |||
$this->assertEquals('user2', $allResults[1]->getUserId()); | |||
} | |||
public function testGetFind(): void { | |||
$this->insertSampleStatuses(); | |||
@@ -84,6 +84,21 @@ class StatusServiceTest extends TestCase { | |||
], $this->service->findAll(20, 50)); | |||
} | |||
public function testFindAllRecentStatusChanges(): void { | |||
$status1 = $this->createMock(UserStatus::class); | |||
$status2 = $this->createMock(UserStatus::class); | |||
$this->mapper->expects($this->once()) | |||
->method('findAllRecent') | |||
->with(20, 50) | |||
->willReturn([$status1, $status2]); | |||
$this->assertEquals([ | |||
$status1, | |||
$status2, | |||
], $this->service->findAllRecentStatusChanges(20, 50)); | |||
} | |||
public function testFindByUserId(): void { | |||
$status = $this->createMock(UserStatus::class); | |||
$this->mapper->expects($this->once()) |
@@ -2,6 +2,7 @@ const path = require('path') | |||
module.exports = { | |||
entry: { | |||
'dashboard': path.join(__dirname, 'src', 'dashboard'), | |||
'user-status-menu': path.join(__dirname, 'src', 'main-user-status-menu') | |||
}, | |||
output: { |
@@ -1684,6 +1684,16 @@ | |||
} | |||
} | |||
}, | |||
"@nextcloud/vue-dashboard": { | |||
"version": "0.1.3", | |||
"resolved": "https://registry.npmjs.org/@nextcloud/vue-dashboard/-/vue-dashboard-0.1.3.tgz", | |||
"integrity": "sha512-7b02zkarX7b18IRQmZEW1NM+dvtcUih2M0+CZyuQfcvfyMQudOz+BdA/oD1p7PmdBds1IR8OvY1+CnpmgAzfQg==", | |||
"requires": { | |||
"@nextcloud/vue": "^2.3.0", | |||
"core-js": "^3.6.4", | |||
"vue": "^2.6.11" | |||
} | |||
}, | |||
"@nodelib/fs.scandir": { | |||
"version": "2.1.3", | |||
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.3.tgz", |
@@ -40,6 +40,7 @@ | |||
"@nextcloud/paths": "^1.1.2", | |||
"@nextcloud/router": "^1.1.0", | |||
"@nextcloud/vue": "^2.3.0", | |||
"@nextcloud/vue-dashboard": "^0.1.3", | |||
"autosize": "^4.0.2", | |||
"backbone": "^1.4.0", | |||
"blueimp-md5": "^2.16.0", |