mirror of
https://github.com/nextcloud/server.git
synced 2026-04-15 22:11:17 -04:00
feat: allow to filter contacts by team
Signed-off-by: Ferdinand Thiessen <opensource@fthiessen.de>
This commit is contained in:
parent
d9d1d04e2e
commit
503acb0ed6
6 changed files with 291 additions and 188 deletions
|
|
@ -31,9 +31,9 @@ const emit = defineEmits<{
|
|||
}>()
|
||||
|
||||
const PresetNames = {
|
||||
// TRANSLATORS Large organization > Big organization > Small organization
|
||||
// TRANSLATORS Large organization > Big organization > Small organization
|
||||
LARGE: t('settings', 'Large organization'),
|
||||
// TRANSLATORS Large organization > Big organization > Small organization
|
||||
// TRANSLATORS Large organization > Big organization > Small organization
|
||||
MEDIUM: t('settings', 'Big organization'),
|
||||
SMALL: t('settings', 'Small organization'),
|
||||
SHARED: t('settings', 'Hosting company'),
|
||||
|
|
|
|||
|
|
@ -13,14 +13,17 @@ use OCP\AppFramework\Http;
|
|||
use OCP\AppFramework\Http\Attribute\FrontpageRoute;
|
||||
use OCP\AppFramework\Http\Attribute\NoAdminRequired;
|
||||
use OCP\AppFramework\Http\JSONResponse;
|
||||
use OCP\Contacts\ContactsMenu\IEntry;
|
||||
use OCP\IRequest;
|
||||
use OCP\IUserSession;
|
||||
use OCP\Teams\ITeamManager;
|
||||
|
||||
class ContactsMenuController extends Controller {
|
||||
public function __construct(
|
||||
IRequest $request,
|
||||
private IUserSession $userSession,
|
||||
private Manager $manager,
|
||||
private ITeamManager $teamManager,
|
||||
) {
|
||||
parent::__construct('core', $request);
|
||||
}
|
||||
|
|
@ -31,8 +34,18 @@ class ContactsMenuController extends Controller {
|
|||
*/
|
||||
#[NoAdminRequired]
|
||||
#[FrontpageRoute(verb: 'POST', url: '/contactsmenu/contacts')]
|
||||
public function index(?string $filter = null): array {
|
||||
return $this->manager->getEntries($this->userSession->getUser(), $filter);
|
||||
public function index(?string $filter = null, ?string $teamId = null): array {
|
||||
$entries = $this->manager->getEntries($this->userSession->getUser(), $filter);
|
||||
if ($teamId !== null) {
|
||||
/** @var \OC\Teams\TeamManager */
|
||||
$teamManager = $this->teamManager;
|
||||
$memberIds = $teamManager->getMembersOfTeam($teamId, $this->userSession->getUser()->getUID());
|
||||
$entries['contacts'] = array_filter(
|
||||
$entries['contacts'],
|
||||
fn (IEntry $entry) => in_array($entry->getProperty('UID'), $memberIds, true)
|
||||
);
|
||||
}
|
||||
return $entries;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -49,4 +62,13 @@ class ContactsMenuController extends Controller {
|
|||
}
|
||||
return new JSONResponse([], Http::STATUS_NOT_FOUND);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \JsonSerializable[]
|
||||
*/
|
||||
#[NoAdminRequired]
|
||||
#[FrontpageRoute(verb: 'GET', url: '/contactsmenu/teams')]
|
||||
public function getTeams(): array {
|
||||
return $this->teamManager->getTeamsForUser($this->userSession->getUser()->getUID());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,12 +3,13 @@
|
|||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { mount, shallowMount } from '@vue/test-utils'
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
import { cleanup, findAllByRole, render } from '@testing-library/vue'
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest'
|
||||
import ContactsMenu from '../../views/ContactsMenu.vue'
|
||||
|
||||
const axios = vi.hoisted(() => ({
|
||||
post: vi.fn(),
|
||||
get: vi.fn(),
|
||||
}))
|
||||
vi.mock('@nextcloud/axios', () => ({ default: axios }))
|
||||
|
||||
|
|
@ -16,67 +17,47 @@ vi.mock('@nextcloud/auth', () => ({
|
|||
getCurrentUser: () => ({ uid: 'user', isAdmin: false, displayName: 'User' }),
|
||||
}))
|
||||
|
||||
afterEach(cleanup)
|
||||
|
||||
describe('ContactsMenu', function() {
|
||||
it('is closed by default', () => {
|
||||
const view = shallowMount(ContactsMenu)
|
||||
|
||||
expect(view.vm.contacts).toEqual([])
|
||||
expect(view.vm.loadingText).toBe(undefined)
|
||||
})
|
||||
|
||||
it('shows a loading text', async () => {
|
||||
const view = shallowMount(ContactsMenu)
|
||||
axios.post.mockResolvedValue({
|
||||
const { promise, resolve } = Promise.withResolvers<void>()
|
||||
axios.post.mockImplementationOnce(async () => (await promise, {
|
||||
data: {
|
||||
contacts: [],
|
||||
contactsAppEnabled: false,
|
||||
},
|
||||
}))
|
||||
axios.get.mockResolvedValue({
|
||||
data: [],
|
||||
})
|
||||
|
||||
const opening = view.vm.handleOpen()
|
||||
const view = render(ContactsMenu)
|
||||
await view.findByRole('button')
|
||||
.then((button) => button.click())
|
||||
|
||||
expect(view.vm.contacts).toEqual([])
|
||||
expect(view.vm.loadingText).toBe('Loading your contacts …')
|
||||
await opening
|
||||
await expect(view.findByText(/Loading your contacts\s…/)).resolves.toBeTruthy()
|
||||
resolve()
|
||||
await expect(view.findByText('No contacts found')).resolves.toBeTruthy()
|
||||
})
|
||||
|
||||
it('shows error view when contacts can not be loaded', async () => {
|
||||
const view = mount(ContactsMenu)
|
||||
axios.post.mockResolvedValue({})
|
||||
axios.get.mockResolvedValue({
|
||||
data: [],
|
||||
})
|
||||
vi.spyOn(console, 'error').mockImplementation(() => {})
|
||||
|
||||
try {
|
||||
await view.vm.handleOpen()
|
||||
|
||||
throw new Error('should not be reached')
|
||||
} catch {
|
||||
expect(console.error).toHaveBeenCalled()
|
||||
console.error.mockRestore()
|
||||
expect(view.vm.error).toBe(true)
|
||||
expect(view.vm.contacts).toEqual([])
|
||||
expect(view.text()).toContain('Could not load your contacts')
|
||||
}
|
||||
})
|
||||
|
||||
it('shows text when there are no contacts', async () => {
|
||||
const view = mount(ContactsMenu)
|
||||
axios.post.mockResolvedValueOnce({
|
||||
data: {
|
||||
contacts: [],
|
||||
contactsAppEnabled: false,
|
||||
},
|
||||
})
|
||||
|
||||
await view.vm.handleOpen()
|
||||
|
||||
expect(view.vm.error).toBe(false)
|
||||
expect(view.vm.contacts).toEqual([])
|
||||
expect(view.vm.loadingText).toBe(undefined)
|
||||
expect(view.text()).toContain('No contacts found')
|
||||
const view = render(ContactsMenu)
|
||||
await view.findByRole('button')
|
||||
.then((button) => button.click())
|
||||
await expect(view.findByText(/Could not load your contacts/)).resolves.toBeTruthy()
|
||||
})
|
||||
|
||||
it('shows contacts', async () => {
|
||||
const view = mount(ContactsMenu)
|
||||
axios.get.mockResolvedValue({
|
||||
data: [],
|
||||
})
|
||||
axios.post.mockResolvedValue({
|
||||
data: {
|
||||
contacts: [
|
||||
|
|
@ -131,12 +112,16 @@ describe('ContactsMenu', function() {
|
|||
},
|
||||
})
|
||||
|
||||
await view.vm.handleOpen()
|
||||
const view = render(ContactsMenu)
|
||||
await view.findByRole('button')
|
||||
.then((button) => button.click())
|
||||
|
||||
expect(view.vm.error).toBe(false)
|
||||
expect(view.vm.contacts.length).toBe(2)
|
||||
expect(view.text()).toContain('Acosta Lancaster')
|
||||
expect(view.text()).toContain('Adeline Snider')
|
||||
expect(view.text()).toContain('Show all contacts')
|
||||
await expect(view.findByRole('list', { name: 'Contacts list' })).resolves.toBeTruthy()
|
||||
const list = view.getByRole('list', { name: 'Contacts list' })
|
||||
await expect(findAllByRole(list, 'listitem')).resolves.toHaveLength(2)
|
||||
|
||||
const items = await findAllByRole(list, 'listitem')
|
||||
expect(items[0]!.textContent).toContain('Acosta Lancaster')
|
||||
expect(items[1]!.textContent).toContain('Adeline Snider')
|
||||
})
|
||||
})
|
||||
|
|
@ -3,28 +3,191 @@
|
|||
- SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
-->
|
||||
|
||||
<script setup lang="ts">
|
||||
import { mdiAccountGroupOutline, mdiContacts, mdiMagnify } from '@mdi/js'
|
||||
import { getCurrentUser } from '@nextcloud/auth'
|
||||
import axios from '@nextcloud/axios'
|
||||
import { getBuilder } from '@nextcloud/browser-storage'
|
||||
import { t } from '@nextcloud/l10n'
|
||||
import { generateUrl } from '@nextcloud/router'
|
||||
import debounce from 'debounce'
|
||||
import { computed, nextTick, onMounted, ref, watch } from 'vue'
|
||||
import NcActionButton from '@nextcloud/vue/components/NcActionButton'
|
||||
import NcActions from '@nextcloud/vue/components/NcActions'
|
||||
import NcButton from '@nextcloud/vue/components/NcButton'
|
||||
import NcEmptyContent from '@nextcloud/vue/components/NcEmptyContent'
|
||||
import NcHeaderMenu from '@nextcloud/vue/components/NcHeaderMenu'
|
||||
import NcIconSvgWrapper from '@nextcloud/vue/components/NcIconSvgWrapper'
|
||||
import NcLoadingIcon from '@nextcloud/vue/components/NcLoadingIcon'
|
||||
import NcTextField from '@nextcloud/vue/components/NcTextField'
|
||||
import ContactMenuEntry from '../components/ContactsMenu/ContactMenuEntry.vue'
|
||||
import logger from '../logger.js'
|
||||
|
||||
const storage = getBuilder('core:contacts')
|
||||
.persist(true)
|
||||
.clearOnLogout(true)
|
||||
.build()
|
||||
|
||||
const user = getCurrentUser()!
|
||||
const contactsAppURL = generateUrl('/apps/contacts')
|
||||
const contactsAppMgmtURL = generateUrl('/settings/apps/social/contacts')
|
||||
|
||||
const contactsMenuInput = ref<HTMLInputElement>()
|
||||
|
||||
const actions = ref(window.OC?.ContactsMenu?.actions || [])
|
||||
const contactsAppEnabled = ref(false)
|
||||
const contacts = ref([])
|
||||
const loadingText = ref<string>()
|
||||
const hasError = ref(false)
|
||||
const searchTerm = ref('')
|
||||
|
||||
const teams = ref<ITeam[]>([])
|
||||
const selectedTeam = ref<string>('$_all_$')
|
||||
const selectedTeamName = computed(() => teams.value.find((t) => t.teamId === selectedTeam.value)?.displayName)
|
||||
|
||||
onMounted(async () => {
|
||||
const team = storage.getItem('core:contacts:team')
|
||||
if (team) {
|
||||
selectedTeam.value = JSON.parse(team)
|
||||
}
|
||||
|
||||
if (userTeams.length === 0) {
|
||||
try {
|
||||
const { data } = await axios.get<ITeam[]>(generateUrl('/contactsmenu/teams'))
|
||||
userTeams.push(...data)
|
||||
} catch (error) {
|
||||
logger.error('could not load user teams', { error })
|
||||
}
|
||||
}
|
||||
teams.value = [...userTeams]
|
||||
})
|
||||
|
||||
watch(selectedTeam, () => {
|
||||
storage.setItem('core:contacts:team', JSON.stringify(selectedTeam.value))
|
||||
getContacts(searchTerm.value)
|
||||
})
|
||||
|
||||
/**
|
||||
* Load contacts when opening the menu
|
||||
*/
|
||||
async function onOpened() {
|
||||
await getContacts('')
|
||||
}
|
||||
|
||||
/**
|
||||
* Load contacts from the server
|
||||
*
|
||||
* @param searchTerm - The search term to filter contacts by
|
||||
*/
|
||||
async function getContacts(searchTerm: string) {
|
||||
if (searchTerm === '') {
|
||||
loadingText.value = t('core', 'Loading your contacts …')
|
||||
} else {
|
||||
loadingText.value = t('core', 'Looking for {term} …', {
|
||||
term: searchTerm,
|
||||
})
|
||||
}
|
||||
|
||||
// Let the user try a different query if the previous one failed
|
||||
hasError.value = false
|
||||
try {
|
||||
const { data } = await axios.post(generateUrl('/contactsmenu/contacts'), {
|
||||
filter: searchTerm,
|
||||
teamId: selectedTeam.value !== '$_all_$' ? selectedTeam.value : undefined,
|
||||
})
|
||||
contacts.value = data.contacts
|
||||
contactsAppEnabled.value = data.contactsAppEnabled
|
||||
loadingText.value = undefined
|
||||
} catch (error) {
|
||||
logger.error('could not load contacts', {
|
||||
error,
|
||||
searchTerm,
|
||||
})
|
||||
hasError.value = true
|
||||
}
|
||||
}
|
||||
|
||||
const onInputDebounced = debounce(function() {
|
||||
getContacts(searchTerm.value)
|
||||
}, 500)
|
||||
|
||||
/**
|
||||
* Reset the search state
|
||||
*/
|
||||
function onReset() {
|
||||
searchTerm.value = ''
|
||||
contacts.value = []
|
||||
focusInput()
|
||||
}
|
||||
|
||||
/**
|
||||
* Focus the search input on next tick
|
||||
*/
|
||||
function focusInput() {
|
||||
nextTick(() => {
|
||||
contactsMenuInput.value?.focus()
|
||||
contactsMenuInput.value?.select()
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
interface ITeam {
|
||||
teamId: string
|
||||
displayName: string
|
||||
link: string
|
||||
}
|
||||
|
||||
const userTeams: ITeam[] = []
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NcHeaderMenu
|
||||
id="contactsmenu"
|
||||
class="contactsmenu"
|
||||
:aria-label="t('core', 'Search contacts')"
|
||||
@open="handleOpen">
|
||||
exclude-click-outside-selectors=".v-popper__popper"
|
||||
@open="onOpened">
|
||||
<template #trigger>
|
||||
<NcIconSvgWrapper class="contactsmenu__trigger-icon" :path="mdiContacts" />
|
||||
</template>
|
||||
<div class="contactsmenu__menu">
|
||||
<div class="contactsmenu__menu__search-container">
|
||||
<div class="contactsmenu__menu__input-wrapper">
|
||||
<NcActions force-menu :aria-label="t('core', 'Filter by team')" variant="tertiary">
|
||||
<template #icon>
|
||||
<NcIconSvgWrapper :path="mdiAccountGroupOutline" />
|
||||
</template>
|
||||
<template #default>
|
||||
<NcActionButton
|
||||
:modelValue.sync="selectedTeam"
|
||||
value="$_all_$"
|
||||
type="radio">
|
||||
{{ t('core', 'All teams') }}
|
||||
</NcActionButton>
|
||||
<NcActionButton
|
||||
v-for="team of teams"
|
||||
:key="team.teamId"
|
||||
:modelValue.sync="selectedTeam"
|
||||
:value="team.teamId"
|
||||
type="radio">
|
||||
{{ team.displayName }}
|
||||
</NcActionButton>
|
||||
</template>
|
||||
</NcActions>
|
||||
<NcTextField
|
||||
id="contactsmenu__menu__search"
|
||||
ref="contactsMenuInput"
|
||||
v-model="searchTerm"
|
||||
class="contactsmenu__menu__search"
|
||||
trailing-button-icon="close"
|
||||
:label="t('core', 'Search contacts')"
|
||||
:label="selectedTeamName
|
||||
? t('core', 'Search contacts in team {team}', { team: selectedTeamName })
|
||||
: t('core', 'Search contacts …')
|
||||
"
|
||||
:trailing-button-label="t('core', 'Reset search')"
|
||||
:show-trailing-button="searchTerm !== ''"
|
||||
:placeholder="t('core', 'Search contacts …')"
|
||||
class="contactsmenu__menu__search"
|
||||
type="search"
|
||||
@input="onInputDebounced"
|
||||
@trailing-button-click="onReset" />
|
||||
</div>
|
||||
|
|
@ -41,7 +204,7 @@
|
|||
</template>
|
||||
</NcButton>
|
||||
</div>
|
||||
<NcEmptyContent v-if="error" :name="t('core', 'Could not load your contacts')">
|
||||
<NcEmptyContent v-if="hasError" :name="t('core', 'Could not load your contacts')">
|
||||
<template #icon>
|
||||
<NcIconSvgWrapper :path="mdiMagnify" />
|
||||
</template>
|
||||
|
|
@ -58,7 +221,7 @@
|
|||
</NcEmptyContent>
|
||||
<div v-else class="contactsmenu__menu__content">
|
||||
<div id="contactsmenu-contacts">
|
||||
<ul>
|
||||
<ul :aria-label="t('core', 'Contacts list')">
|
||||
<ContactMenuEntry v-for="contact in contacts" :key="contact.id" :contact="contact" />
|
||||
</ul>
|
||||
</div>
|
||||
|
|
@ -67,7 +230,7 @@
|
|||
{{ t('core', 'Show all contacts') }}
|
||||
</NcButton>
|
||||
</div>
|
||||
<div v-else-if="canInstallApp" class="contactsmenu__menu__content__footer">
|
||||
<div v-else-if="user.isAdmin" class="contactsmenu__menu__content__footer">
|
||||
<NcButton variant="tertiary" :href="contactsAppMgmtURL">
|
||||
{{ t('core', 'Install the Contacts app') }}
|
||||
</NcButton>
|
||||
|
|
@ -77,120 +240,6 @@
|
|||
</NcHeaderMenu>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mdiContacts, mdiMagnify } from '@mdi/js'
|
||||
import { getCurrentUser } from '@nextcloud/auth'
|
||||
import axios from '@nextcloud/axios'
|
||||
import { t } from '@nextcloud/l10n'
|
||||
import { generateUrl } from '@nextcloud/router'
|
||||
import debounce from 'debounce'
|
||||
import NcButton from '@nextcloud/vue/components/NcButton'
|
||||
import NcEmptyContent from '@nextcloud/vue/components/NcEmptyContent'
|
||||
import NcHeaderMenu from '@nextcloud/vue/components/NcHeaderMenu'
|
||||
import NcIconSvgWrapper from '@nextcloud/vue/components/NcIconSvgWrapper'
|
||||
import NcLoadingIcon from '@nextcloud/vue/components/NcLoadingIcon'
|
||||
import NcTextField from '@nextcloud/vue/components/NcTextField'
|
||||
import ContactMenuEntry from '../components/ContactsMenu/ContactMenuEntry.vue'
|
||||
import logger from '../logger.js'
|
||||
import Nextcloud from '../mixins/Nextcloud.js'
|
||||
|
||||
export default {
|
||||
name: 'ContactsMenu',
|
||||
|
||||
components: {
|
||||
ContactMenuEntry,
|
||||
NcButton,
|
||||
NcEmptyContent,
|
||||
NcHeaderMenu,
|
||||
NcIconSvgWrapper,
|
||||
NcLoadingIcon,
|
||||
NcTextField,
|
||||
},
|
||||
|
||||
mixins: [Nextcloud],
|
||||
|
||||
setup() {
|
||||
return {
|
||||
mdiContacts,
|
||||
mdiMagnify,
|
||||
}
|
||||
},
|
||||
|
||||
data() {
|
||||
const user = getCurrentUser()
|
||||
return {
|
||||
actions: window.OC?.ContactsMenu?.actions || [],
|
||||
contactsAppEnabled: false,
|
||||
contactsAppURL: generateUrl('/apps/contacts'),
|
||||
contactsAppMgmtURL: generateUrl('/settings/apps/social/contacts'),
|
||||
canInstallApp: user.isAdmin,
|
||||
contacts: [],
|
||||
loadingText: undefined,
|
||||
error: false,
|
||||
searchTerm: '',
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
async handleOpen() {
|
||||
await this.getContacts('')
|
||||
},
|
||||
|
||||
async getContacts(searchTerm) {
|
||||
if (searchTerm === '') {
|
||||
this.loadingText = t('core', 'Loading your contacts …')
|
||||
} else {
|
||||
this.loadingText = t('core', 'Looking for {term} …', {
|
||||
term: searchTerm,
|
||||
})
|
||||
}
|
||||
|
||||
// Let the user try a different query if the previous one failed
|
||||
this.error = false
|
||||
|
||||
try {
|
||||
const { data: { contacts, contactsAppEnabled } } = await axios.post(generateUrl('/contactsmenu/contacts'), {
|
||||
filter: searchTerm,
|
||||
})
|
||||
this.contacts = contacts
|
||||
this.contactsAppEnabled = contactsAppEnabled
|
||||
this.loadingText = undefined
|
||||
} catch (error) {
|
||||
logger.error('could not load contacts', {
|
||||
error,
|
||||
searchTerm,
|
||||
})
|
||||
this.error = true
|
||||
}
|
||||
},
|
||||
|
||||
onInputDebounced: debounce(function() {
|
||||
this.getContacts(this.searchTerm)
|
||||
}, 500),
|
||||
|
||||
/**
|
||||
* Reset the search state
|
||||
*/
|
||||
onReset() {
|
||||
this.searchTerm = ''
|
||||
this.contacts = []
|
||||
this.focusInput()
|
||||
},
|
||||
|
||||
/**
|
||||
* Focus the search input on next tick
|
||||
*/
|
||||
focusInput() {
|
||||
this.$nextTick(() => {
|
||||
this.$refs.contactsMenuInput.focus()
|
||||
this.$refs.contactsMenuInput.select()
|
||||
})
|
||||
},
|
||||
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.contactsmenu {
|
||||
overflow-y: hidden;
|
||||
|
|
@ -206,12 +255,6 @@ export default {
|
|||
height: calc(50px * 6 + 2px + 26px);
|
||||
max-height: inherit;
|
||||
|
||||
label[for="contactsmenu__menu__search"] {
|
||||
font-weight: bold;
|
||||
font-size: 19px;
|
||||
margin-inline-start: 13px;
|
||||
}
|
||||
|
||||
&__search-container {
|
||||
padding: 10px;
|
||||
display: flex;
|
||||
|
|
@ -223,6 +266,8 @@ export default {
|
|||
z-index: 2;
|
||||
top: 0;
|
||||
flex-grow: 1;
|
||||
display: flex;
|
||||
gap: var(--default-grid-baseline);
|
||||
}
|
||||
|
||||
&__search {
|
||||
|
|
|
|||
|
|
@ -132,6 +132,18 @@ class TeamManager implements ITeamManager {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public function getMembersOfTeam(string $teamId, string $userId): array {
|
||||
$team = $this->getTeam($teamId, $userId);
|
||||
if ($team === null) {
|
||||
return [];
|
||||
}
|
||||
$members = $team->getInheritedMembers();
|
||||
return array_map(fn ($member) => $member->getUserId(), $members);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Circle[]
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ namespace Tests\Controller;
|
|||
|
||||
use OC\Contacts\ContactsMenu\Manager;
|
||||
use OC\Core\Controller\ContactsMenuController;
|
||||
use OC\Teams\TeamManager;
|
||||
use OCP\Contacts\ContactsMenu\IEntry;
|
||||
use OCP\IRequest;
|
||||
use OCP\IUser;
|
||||
|
|
@ -17,11 +18,9 @@ use PHPUnit\Framework\MockObject\MockObject;
|
|||
use Test\TestCase;
|
||||
|
||||
class ContactsMenuControllerTest extends TestCase {
|
||||
/** @var IUserSession|MockObject */
|
||||
private $userSession;
|
||||
|
||||
/** @var Manager|MockObject */
|
||||
private $contactsManager;
|
||||
private IUserSession&MockObject $userSession;
|
||||
private Manager&MockObject $contactsManager;
|
||||
private TeamManager&MockObject $teamManager;
|
||||
|
||||
private ContactsMenuController $controller;
|
||||
|
||||
|
|
@ -31,8 +30,14 @@ class ContactsMenuControllerTest extends TestCase {
|
|||
$request = $this->createMock(IRequest::class);
|
||||
$this->userSession = $this->createMock(IUserSession::class);
|
||||
$this->contactsManager = $this->createMock(Manager::class);
|
||||
$this->teamManager = $this->createMock(TeamManager::class);
|
||||
|
||||
$this->controller = new ContactsMenuController($request, $this->userSession, $this->contactsManager);
|
||||
$this->controller = new ContactsMenuController(
|
||||
$request,
|
||||
$this->userSession,
|
||||
$this->contactsManager,
|
||||
$this->teamManager,
|
||||
);
|
||||
}
|
||||
|
||||
public function testIndex(): void {
|
||||
|
|
@ -54,6 +59,40 @@ class ContactsMenuControllerTest extends TestCase {
|
|||
$this->assertEquals($entries, $response);
|
||||
}
|
||||
|
||||
public function testIndex_withTeam(): void {
|
||||
$user = $this->createMock(IUser::class);
|
||||
$user->method('getUID')
|
||||
->willReturn('current-user');
|
||||
|
||||
$entries = [
|
||||
$this->createMock(IEntry::class),
|
||||
$this->createMock(IEntry::class),
|
||||
];
|
||||
$entries[0]->method('getProperty')
|
||||
->with('UID')
|
||||
->willReturn('member1');
|
||||
$entries[0]->method('getProperty')
|
||||
->with('UID')
|
||||
->willReturn('member2');
|
||||
|
||||
$this->userSession->expects($this->atLeastOnce())
|
||||
->method('getUser')
|
||||
->willReturn($user);
|
||||
$this->contactsManager->expects($this->once())
|
||||
->method('getEntries')
|
||||
->with($this->equalTo($user), $this->equalTo(null))
|
||||
->willReturn(['contacts' => $entries]);
|
||||
|
||||
$this->teamManager->expects($this->once())
|
||||
->method('getMembersOfTeam')
|
||||
->with('team-id', 'current-user')
|
||||
->willReturn(['member1', 'member3']);
|
||||
|
||||
$response = $this->controller->index(teamId: 'team-id');
|
||||
|
||||
$this->assertEquals([$entries[0]], $response['contacts']);
|
||||
}
|
||||
|
||||
public function testFindOne(): void {
|
||||
$user = $this->createMock(IUser::class);
|
||||
$entry = $this->createMock(IEntry::class);
|
||||
|
|
|
|||
Loading…
Reference in a new issue