Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 24 additions & 2 deletions core/Controller/ContactsMenuController.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,17 @@
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);
}
Expand All @@ -31,8 +34,18 @@ public function __construct(
*/
#[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;
}

/**
Expand All @@ -49,4 +62,13 @@ public function findOne(int $shareType, string $shareWith) {
}
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());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,80 +3,61 @@
* 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 }))

vi.mock('@nextcloud/auth', () => ({
getCurrentUser: () => ({ uid: 'user', isAdmin: false, displayName: 'User' }),
}))

describe('ContactsMenu', function() {
it('is closed by default', () => {
const view = shallowMount(ContactsMenu)

expect(view.vm.contacts).toEqual([])
expect(view.vm.loadingText).toBe(undefined)
})
afterEach(cleanup)

describe('ContactsMenu', function() {
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({})
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,
},
axios.get.mockResolvedValue({
data: [],
})
vi.spyOn(console, 'error').mockImplementation(() => {})

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: [
Expand Down Expand Up @@ -131,12 +112,16 @@ describe('ContactsMenu', function() {
},
})

await view.vm.handleOpen()
const view = render(ContactsMenu)
await view.findByRole('button')
.then((button) => button.click())

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)

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')
const items = await findAllByRole(list, 'listitem')
expect(items[0]!.textContent).toContain('Acosta Lancaster')
expect(items[1]!.textContent).toContain('Adeline Snider')
})
})
Loading