feat: Allow disabling of threaded view

Signed-off-by: SebastianKrupinski <krupinskis05@gmail.com>
This commit is contained in:
SebastianKrupinski
2025-03-29 17:03:43 -04:00
parent f0aaa1e970
commit 084cc6768a
23 changed files with 289 additions and 75 deletions

View File

@ -345,6 +345,11 @@ return [
'url' => '/api/settings/importance-classification-default',
'verb' => 'PUT'
],
[
'name' => 'settings#setLayoutMessageView',
'url' => '/api/settings/layout-message-view',
'verb' => 'PUT'
],
[
'name' => 'trusted_senders#setTrusted',
'url' => '/api/trustedsenders/{email}',

View File

@ -21,6 +21,8 @@ use OCP\IUser;
interface IMailSearch {
public const ORDER_NEWEST_FIRST = 'DESC';
public const ORDER_OLDEST_FIRST = 'ASC';
public const VIEW_SINGLETON = 'singleton';
public const VIEW_THREADED = 'threaded';
/**
* @throws DoesNotExistException
* @throws ClientException
@ -37,6 +39,7 @@ interface IMailSearch {
* @param string|null $filter
* @param int|null $cursor
* @param int|null $limit
* @param string|null $view
*
* @return Message[]
*
@ -48,7 +51,8 @@ interface IMailSearch {
string $sortOrder,
?string $filter,
?int $cursor,
?int $limit): array;
?int $limit,
?string $view): array;
/**
* Run a search through all mailboxes of a user.

View File

@ -124,6 +124,7 @@ class MessagesController extends Controller {
* @param int $cursor
* @param string $filter
* @param int|null $limit
* @param string $view returns messages in requested view ('singleton' or 'threaded')
*
* @return JSONResponse
*
@ -134,7 +135,8 @@ class MessagesController extends Controller {
public function index(int $mailboxId,
?int $cursor = null,
?string $filter = null,
?int $limit = null): JSONResponse {
?int $limit = null,
?string $view = null): JSONResponse {
try {
$mailbox = $this->mailManager->getMailbox($this->currentUserId, $mailboxId);
$account = $this->accountService->find($this->currentUserId, $mailbox->getAccountId());
@ -143,17 +145,21 @@ class MessagesController extends Controller {
}
$this->logger->debug("loading messages of mailbox <$mailboxId>");
$sort = $this->preferences->getPreference($this->currentUserId, 'sort-order', 'newest') === 'newest' ? IMailSearch::ORDER_NEWEST_FIRST: IMailSearch::ORDER_OLDEST_FIRST;
$view = $view === 'singleton' ? IMailSearch::VIEW_SINGLETON : IMailSearch::VIEW_THREADED;
$order = $this->preferences->getPreference($this->currentUserId, 'sort-order', 'newest') === 'newest' ? 'DESC': 'ASC';
$messages = $this->mailSearch->findMessages(
$account,
$mailbox,
$sort,
$filter === '' ? null : $filter,
$cursor,
$limit,
$view
);
return new JSONResponse(
$this->mailSearch->findMessages(
$account,
$mailbox,
$order,
$filter === '' ? null : $filter,
$cursor,
$limit
)
$messages
);
}

View File

@ -211,6 +211,7 @@ class PageController extends Controller {
'app-version' => $this->config->getAppValue('mail', 'installed_version'),
'external-avatars' => $this->preferences->getPreference($this->currentUserId, 'external-avatars', 'true'),
'layout-mode' => $this->preferences->getPreference($this->currentUserId, 'layout-mode', 'vertical-split'),
'layout-message-view' => $this->preferences->getPreference($this->currentUserId, 'layout-message-view', $this->config->getAppValue('mail', 'layout_message_view', 'threaded')),
'reply-mode' => $this->preferences->getPreference($this->currentUserId, 'reply-mode', 'top'),
'collect-data' => $this->preferences->getPreference($this->currentUserId, 'collect-data', 'true'),
'search-priority-body' => $this->preferences->getPreference($this->currentUserId, 'search-priority-body', 'false'),

View File

@ -124,4 +124,9 @@ class SettingsController extends Controller {
return new JSONResponse([]);
}
public function setLayoutMessageView(string $value): JSONResponse {
$this->config->setAppValue('mail', 'layout_message_view', $value);
return new JSONResponse([]);
}
}

View File

@ -808,21 +808,23 @@ class MessageMapper extends QBMapper {
} else {
$select = $qb->select(['m.id', 'm.sent_at']);
}
$select->from($this->getTableName(), 'm');
$selfJoin = $select->expr()->andX(
$select->expr()->eq('m.mailbox_id', 'm2.mailbox_id', IQueryBuilder::PARAM_INT),
$select->expr()->eq('m.thread_root_id', 'm2.thread_root_id', IQueryBuilder::PARAM_INT),
$select->expr()->orX(
$select->expr()->lt('m.sent_at', 'm2.sent_at', IQueryBuilder::PARAM_INT),
$select->expr()->andX(
$select->expr()->eq('m.sent_at', 'm2.sent_at', IQueryBuilder::PARAM_INT),
$select->expr()->lt('m.message_id', 'm2.message_id', IQueryBuilder::PARAM_STR),
if ($query->getThreaded()) {
$selfJoin = $select->expr()->andX(
$select->expr()->eq('m.mailbox_id', 'm2.mailbox_id', IQueryBuilder::PARAM_INT),
$select->expr()->eq('m.thread_root_id', 'm2.thread_root_id', IQueryBuilder::PARAM_INT),
$select->expr()->orX(
$select->expr()->lt('m.sent_at', 'm2.sent_at', IQueryBuilder::PARAM_INT),
$select->expr()->andX(
$select->expr()->eq('m.sent_at', 'm2.sent_at', IQueryBuilder::PARAM_INT),
$select->expr()->lt('m.message_id', 'm2.message_id', IQueryBuilder::PARAM_STR),
),
),
),
);
$select->from($this->getTableName(), 'm')
->leftJoin('m', $this->getTableName(), 'm2', $selfJoin);
);
$select->leftJoin('m', $this->getTableName(), 'm2', $selfJoin);
}
if (!empty($query->getFrom())) {
$select->innerJoin('m', 'mail_recipients', 'r0', 'm.id = r0.message_id');
@ -1008,7 +1010,9 @@ class MessageMapper extends QBMapper {
);
}
$select->andWhere($qb->expr()->isNull('m2.id'));
if ($query->getThreaded()) {
$select->andWhere($qb->expr()->isNull('m2.id'));
}
if ($sortOrder === 'ASC') {
$select->orderBy('m.sent_at', $sortOrder);

View File

@ -10,6 +10,7 @@ declare(strict_types=1);
namespace OCA\Mail\Listener;
use Generator;
use OCA\Mail\Contracts\IUserPreferences;
use OCA\Mail\Db\MessageMapper;
use OCA\Mail\Events\SynchronizationEvent;
use OCA\Mail\IMAP\Threading\Container;
@ -17,6 +18,7 @@ use OCA\Mail\IMAP\Threading\DatabaseMessage;
use OCA\Mail\IMAP\Threading\ThreadBuilder;
use OCP\EventDispatcher\Event;
use OCP\EventDispatcher\IEventListener;
use function array_chunk;
use function gc_collect_cycles;
use function iterator_to_array;
@ -27,16 +29,11 @@ use function iterator_to_array;
class AccountSynchronizedThreadUpdaterListener implements IEventListener {
private const WRITE_IDS_CHUNK_SIZE = 500;
/** @var MessageMapper */
private $mapper;
/** @var ThreadBuilder */
private $builder;
public function __construct(MessageMapper $mapper,
ThreadBuilder $builder) {
$this->mapper = $mapper;
$this->builder = $builder;
public function __construct(
private IUserPreferences $preferences,
private MessageMapper $mapper,
private ThreadBuilder $builder,
) {
}
public function handle(Event $event): void {
@ -45,6 +42,13 @@ class AccountSynchronizedThreadUpdaterListener implements IEventListener {
return;
}
$logger = $event->getLogger();
$userId = $event->getAccount()->getUserId();
if ($this->preferences->getPreference($userId, 'layout-message-view', 'threaded') !== 'threaded') {
$event->getLogger()->debug('Skipping threading as the user prefers a flat view');
return;
}
if (!$event->isRebuildThreads()) {
$event->getLogger()->debug('Skipping threading as there were no significant changes');
return;

View File

@ -80,6 +80,7 @@ class MailSearch implements IMailSearch {
* @param string|null $filter
* @param int|null $cursor
* @param int|null $limit
* @param string|null $view
*
* @return Message[]
*
@ -91,7 +92,8 @@ class MailSearch implements IMailSearch {
string $sortOrder,
?string $filter,
?int $cursor,
?int $limit): array {
?int $limit,
?string $view): array {
if ($mailbox->hasLocks($this->timeFactory->getTime())) {
throw MailboxLockedException::from($mailbox);
}
@ -103,6 +105,9 @@ class MailSearch implements IMailSearch {
if ($cursor !== null) {
$query->setCursor($cursor);
}
if ($view !== null) {
$query->setThreaded($view === self::VIEW_THREADED);
}
// In flagged we don't want anything but flagged messages
if ($mailbox->isSpecialUse(Horde_Imap_Client::SPECIALUSE_FLAGGED)) {
$query->addFlag(Flag::is(Flag::FLAGGED));

View File

@ -12,6 +12,8 @@ namespace OCA\Mail\Service\Search;
class SearchQuery {
/** @var int|null */
private $cursor;
private bool $threaded = true;
/** @var Flag[] */
private $flags = [];
@ -72,6 +74,14 @@ class SearchQuery {
$this->cursor = $cursor;
}
public function getThreaded(): bool {
return $this->threaded;
}
public function setThreaded(bool $threaded): void {
$this->threaded = $threaded;
}
public function getMatch(): string {
return $this->match;
}

View File

@ -80,6 +80,12 @@ class AdminSettings implements ISettings {
$this->config->getAppValue('mail', 'allow_new_mail_accounts', 'yes') === 'yes'
);
$this->initialStateService->provideInitialState(
Application::APP_ID,
'layout_message_view',
$this->config->getAppValue('mail', 'layout_message_view', 'threaded')
);
$this->initialStateService->provideInitialState(
Application::APP_ID,
'llm_processing',

View File

@ -72,6 +72,24 @@
{{ t('mail', 'Horizontal split') }}
</NcCheckboxRadioSwitch>
<h6>{{ t('mail', 'Message View Mode') }}</h6>
<div class="sorting">
<NcCheckboxRadioSwitch type="radio"
name="message_view_mode_radio"
value="threaded"
:checked="layoutMessageView"
@update:checked="setLayoutMessageView('threaded')">
{{ t('mail', 'Show all messages in thread') }}
</NcCheckboxRadioSwitch>
<NcCheckboxRadioSwitch type="radio"
name="message_view_mode_radio"
value="singleton"
:checked="layoutMessageView"
@update:checked="setLayoutMessageView('singleton')">
{{ t('mail', 'Show only the selected message') }}
</NcCheckboxRadioSwitch>
</div>
<h6>{{ t('mail', 'Sorting') }}</h6>
<div class="sorting">
<NcCheckboxRadioSwitch class="sorting__switch"
@ -398,6 +416,9 @@ export default {
layoutMode() {
return this.mainStore.getPreference('layout-mode', 'vertical-split')
},
layoutMessageView() {
return this.mainStore.getPreference('layout-message-view')
},
},
watch: {
showSettings(value) {
@ -439,6 +460,16 @@ export default {
Logger.error('Could not save preferences', { error })
}
},
async setLayoutMessageView(value) {
try {
await this.mainStore.savePreference({
key: 'layout-message-view',
value,
})
} catch (error) {
Logger.error('Could not save preferences', { error })
}
},
async onOpen() {
this.showSettings = true
},

View File

@ -190,7 +190,12 @@
<template #icon>
<OpenInNewIcon :size="16" />
</template>
{{ t('mail', 'Move thread') }}
<template v-if="layoutMessageViewThreaded">
{{ t('mail', 'Move thread') }}
</template>
<template v-else>
{{ t('mail', 'Move Message') }}
</template>
</ActionButton>
<ActionButton v-if="showArchiveButton && hasArchiveAcl"
:close-after-click="true"
@ -199,7 +204,12 @@
<template #icon>
<ArchiveIcon :size="16" />
</template>
{{ t('mail', 'Archive thread') }}
<template v-if="layoutMessageViewThreaded">
{{ t('mail', 'Archive thread') }}
</template>
<template v-else>
{{ t('mail', 'Archive message') }}
</template>
</ActionButton>
<ActionButton v-if="hasDeleteAcl"
:close-after-click="true"
@ -207,7 +217,12 @@
<template #icon>
<DeleteIcon :size="16" />
</template>
{{ t('mail', 'Delete thread') }}
<template v-if="layoutMessageViewThreaded">
{{ t('mail', 'Delete thread') }}
</template>
<template v-else>
{{ t('mail', 'Delete message') }}
</template>
</ActionButton>
<ActionButton :close-after-click="false"
@click="showMoreActionOptions">
@ -311,7 +326,7 @@
<MoveModal v-if="showMoveModal"
:account="account"
:envelopes="[data]"
:move-thread="true"
:move-thread="listViewThreaded"
@move="onMove"
@close="onCloseMoveModal" />
<EventModal v-if="showEventModal"
@ -493,6 +508,9 @@ export default {
oneLineLayout() {
return this.overwriteOneLineMobile ? false : this.mainStore.getPreference('layout-mode', 'vertical-split') === 'no-split'
},
layoutMessageViewThreaded() {
return this.mainStore.getPreference('layout-message-view', 'threaded') === 'threaded'
},
hasMultipleRecipients() {
if (!this.account) {
console.error('account is undefined', {
@ -773,9 +791,15 @@ export default {
this.$emit('delete', this.data.databaseId)
try {
await this.mainStore.deleteThread({
envelope: this.data,
})
if (this.layoutMessageViewThreaded) {
await this.mainStore.deleteThread({
envelope: this.data,
})
} else {
await this.mainStore.deleteMessage({
id: this.data.databaseId,
})
}
} catch (error) {
showError(await matchError(error, {
[NoTrashMailboxConfiguredError.getName()]() {
@ -807,10 +831,17 @@ export default {
this.$emit('archive', this.data.databaseId)
try {
await this.mainStore.moveThread({
envelope: this.data,
destMailboxId: this.account.archiveMailboxId,
})
if (this.layoutMessageViewThreaded) {
await this.mainStore.moveThread({
envelope: this.data,
destMailboxId: this.account.archiveMailboxId,
})
} else {
await this.mainStore.moveMessage({
id: this.data.databaseId,
destMailboxId: this.account.archiveMailboxId,
})
}
} catch (error) {
logger.error('could not archive message', error)
showError(t('mail', 'Could not archive message'))
@ -825,11 +856,19 @@ export default {
}
try {
await this.mainStore.snoozeThread({
envelope: this.data,
unixTimestamp: timestamp / 1000,
destMailboxId: this.account.snoozeMailboxId,
})
if (this.layoutMessageViewThreaded) {
await this.mainStore.snoozeThread({
envelope: this.data,
unixTimestamp: timestamp / 1000,
destMailboxId: this.account.snoozeMailboxId,
})
} else {
await this.mainStore.snoozeMessage({
id: this.data.databaseId,
unixTimestamp: timestamp / 1000,
destMailboxId: this.account.snoozeMailboxId,
})
}
showSuccess(t('mail', 'Thread was snoozed'))
} catch (error) {
logger.error('could not snooze thread', error)
@ -841,9 +880,15 @@ export default {
this.setSelected(false)
try {
await this.mainStore.unSnoozeThread({
envelope: this.data,
})
if (this.layoutMessageViewThreaded) {
await this.mainStore.unSnoozeThread({
envelope: this.data,
})
} else {
await this.mainStore.unSnoozeMessage({
id: this.data.databaseId,
})
}
showSuccess(t('mail', 'Thread was unsnoozed'))
} catch (error) {
logger.error('Could not unsnooze thread', error)

View File

@ -8,6 +8,7 @@
:id="genId(mailbox)"
:key="genId(mailbox)"
v-droppable-mailbox="{
mainStore: mainStore,
mailboxId: mailbox.databaseId,
accountId: mailbox.accountId,
isValidDropTarget,

View File

@ -126,6 +126,10 @@ export default {
return []
}
if (this.mainStore.getPreference('layout-message-view', 'threaded') === 'singleton') {
return [envelope]
}
const envelopes = this.mainStore.getEnvelopesByThreadRootId(envelope.accountId, envelope.threadRootId)
if (envelopes.length === 0) {
return []
@ -281,11 +285,12 @@ export default {
this.expandedThreads = [this.threadId]
this.errorMessage = ''
this.error = undefined
await this.fetchThread()
if (this.mainStore.getPreference('layout-message-view', 'threaded') === 'threaded') {
await this.fetchThread()
}
this.updateParticipantsToDisplay()
this.updateSummary()
this.loadedThreads = 0
},
async fetchThread() {
this.loading = true

View File

@ -253,6 +253,36 @@
</article>
<MicrosoftAdminOauthSettings :tenant-id="microsoftOauthTenantId" :client-id="microsoftOauthClientId" />
</div>
<div class="app-description">
<h3>{{ t('mail', 'User Interface Preference Defaults') }}</h3>
<article>
<p>
{{ t('mail', 'These settings are used to pre-configure the user interface preferences they can be overridden by the user in the mail settings') }}
</p>
</article>
<br>
<article>
<p>
{{ t('mail', 'Message View Mode') }}
</p>
<p>
<NcCheckboxRadioSwitch type="radio"
name="message_view_mode_radio"
value="threaded"
:checked.sync="layoutMessageView"
@update:checked="setLayoutMessageView('threaded')">
{{ t('mail', 'Show all messages in thread') }}
</NcCheckboxRadioSwitch>
<NcCheckboxRadioSwitch type="radio"
name="message_view_mode_radio"
value="singleton"
:checked.sync="layoutMessageView"
@update:checked="setLayoutMessageView('singleton')">
{{ t('mail', 'Show only the selected message') }}
</NcCheckboxRadioSwitch>
</p>
</article>
</div>
</SettingsSection>
</template>
@ -278,6 +308,7 @@ import {
updateLlmEnabled,
updateEnabledSmartReply,
setImportanceClassificationEnabledByDefault,
setLayoutMessageView,
} from '../../service/SettingsService.js'
const googleOauthClientId = loadState('mail', 'google_oauth_client_id', null) ?? undefined
@ -342,6 +373,7 @@ export default {
isLlmFreePromptConfigured: loadState('mail', 'enabled_llm_free_prompt_backend'),
isClassificationEnabledByDefault: loadState('mail', 'llm_processing', true),
isImportanceClassificationEnabledByDefault: loadState('mail', 'importance_classification_default', true),
layoutMessageView: loadState('mail', 'layout_message_view'),
}
},
methods: {
@ -410,6 +442,9 @@ export default {
logger.error('Could not save default classification setting', { error })
}
},
async setLayoutMessageView(value) {
await setLayoutMessageView(value)
},
},
}
</script>

View File

@ -2,20 +2,15 @@
* SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
import useMainStore from '../../../store/mainStore.js'
import logger from '../../../logger.js'
import dragEventBus from '../util/dragEventBus.js'
import { createPinia, setActivePinia } from 'pinia'
setActivePinia(createPinia())
const mainStore = useMainStore()
export class DroppableMailbox {
constructor(el, componentInstance, options) {
this.el = el
this.options = options
this.mainStore = options.mainStore
this.registerListeners.bind(this)(el)
this.setInitialAttributes()
}
@ -149,10 +144,17 @@ export class DroppableMailbox {
item.setAttribute('draggable-envelope', 'pending')
try {
await mainStore.moveThread({
envelope,
destMailboxId: this.options.mailboxId,
})
if (this.mainStore.getPreference('layout-message-view') === 'threaded') {
await this.mainStore.moveThread({
envelope,
destMailboxId: this.options.mailboxId,
})
} else {
await this.mainStore.moveMessage({
id: envelope.databaseId,
destMailboxId: this.options.mailboxId,
})
}
} catch (error) {
item.removeAttribute('draggable-envelope')
logger.error('could not move messages', error)

View File

@ -70,6 +70,10 @@ export default function initAfterAppCreation() {
key: 'layout-mode',
value: preferences['layout-mode'],
})
mainStore.savePreferenceMutation({
key: 'layout-message-view',
value: preferences['layout-message-view'],
})
mainStore.savePreferenceMutation({
key: 'follow-up-reminders',
value: preferences['follow-up-reminders'],

View File

@ -32,7 +32,7 @@ export function fetchEnvelope(accountId, id) {
})
}
export function fetchEnvelopes(accountId, mailboxId, query, cursor, limit) {
export function fetchEnvelopes(accountId, mailboxId, query, cursor, limit, sort, view) {
const url = generateUrl('/apps/mail/api/messages')
const params = {
mailboxId,
@ -47,6 +47,12 @@ export function fetchEnvelopes(accountId, mailboxId, query, cursor, limit) {
if (cursor) {
params.cursor = cursor
}
if (sort) {
params.sort = sort
}
if (view) {
params.view = view
}
return axios
.get(url, {

View File

@ -89,3 +89,16 @@ export const setImportanceClassificationEnabledByDefault = async (enabledByDefau
enabledByDefault,
})
}
/**
* @param {boolean} value
* @return {Promise<void>}
*/
export const setLayoutMessageView = async (value) => {
const url = generateUrl('/apps/mail/api/settings/layout-message-view')
const data = {
value,
}
const resp = await axios.put(url, data)
return resp.data
}

View File

@ -667,6 +667,8 @@ export default function mainStoreActions() {
mailboxId: mb.databaseId,
query,
addToUnifiedMailboxes: false,
sort: this.getPreference('sort-order'),
view: this.getPreference('layout-message-view'),
}),
),
Promise.all.bind(Promise),
@ -701,7 +703,7 @@ export default function mainStoreActions() {
}),
),
),
)(mailbox.accountId, mailboxId, query, undefined, PAGE_SIZE, this.getPreference('sort-order'))
)(mailbox.accountId, mailboxId, query, undefined, PAGE_SIZE, this.getPreference('sort-order'), this.getPreference('layout-message-view'))
})
},
async fetchNextEnvelopePage({
@ -1819,12 +1821,18 @@ export default function mainStoreActions() {
* @return {Array} list of envelope ids
*/
appendOrReplaceEnvelopeId(existing, envelope) {
const index = existing.findIndex((id) => this.envelopes[id].threadRootId === envelope.threadRootId)
if (index === -1) {
if (this.getPreference('layout-message-view') === 'singleton') {
existing.push(envelope.databaseId)
} else {
existing[index] = envelope.databaseId
const index = existing.findIndex((id) => this.envelopes[id].threadRootId === envelope.threadRootId)
if (index === -1) {
existing.push(envelope.databaseId)
} else {
existing[index] = envelope.databaseId
}
}
return existing
},
savePreferenceMutation({

View File

@ -168,7 +168,7 @@ class PageControllerTest extends TestCase {
$account1 = $this->createMock(Account::class);
$account2 = $this->createMock(Account::class);
$mailbox = $this->createMock(Mailbox::class);
$this->preferences->expects($this->exactly(10))
$this->preferences->expects($this->exactly(11))
->method('getPreference')
->willReturnMap([
[$this->userId, 'account-settings', '[]', json_encode([])],
@ -179,6 +179,7 @@ class PageControllerTest extends TestCase {
[$this->userId, 'search-priority-body', 'false', 'false'],
[$this->userId, 'start-mailbox-id', null, '123'],
[$this->userId, 'layout-mode', 'vertical-split', 'vertical-split'],
[$this->userId, 'layout-message-view', 'threaded', 'threaded'],
[$this->userId, 'follow-up-reminders', 'true', 'true'],
[$this->userId, 'internal-addresses', 'false', 'false'],
]);
@ -257,10 +258,11 @@ class PageControllerTest extends TestCase {
['version', '0.0.0', '26.0.0'],
['app.mail.attachment-size-limit', 0, 123],
]);
$this->config->expects($this->exactly(6))
$this->config->expects($this->exactly(7))
->method('getAppValue')
->withConsecutive(
[ 'mail', 'installed_version' ],
['mail', 'layout_message_view' ],
['mail', 'google_oauth_client_id' ],
['mail', 'microsoft_oauth_client_id' ],
['mail', 'microsoft_oauth_tenant_id' ],
@ -268,6 +270,7 @@ class PageControllerTest extends TestCase {
['mail', 'allow_new_mail_accounts', 'yes'],
)->willReturnOnConsecutiveCalls(
$this->returnValue('1.2.3'),
$this->returnValue('threaded'),
$this->returnValue(''),
$this->returnValue(''),
$this->returnValue(''),
@ -321,6 +324,7 @@ class PageControllerTest extends TestCase {
'tag-classified-messages' => 'false',
'search-priority-body' => 'false',
'layout-mode' => 'vertical-split',
'layout-message-view' => 'threaded',
'follow-up-reminders' => 'true',
]],
['prefill_displayName', 'Jane Doe'],

View File

@ -81,6 +81,7 @@ class MailSearchTest extends TestCase {
'DESC',
null,
null,
null,
null
);
}
@ -97,6 +98,7 @@ class MailSearchTest extends TestCase {
'DESC',
null,
null,
null,
null
);
}
@ -117,6 +119,7 @@ class MailSearchTest extends TestCase {
'DESC',
null,
null,
null,
null
);
@ -156,6 +159,7 @@ class MailSearchTest extends TestCase {
'DESC',
'my search',
null,
null,
null
);
@ -198,6 +202,7 @@ class MailSearchTest extends TestCase {
'DESC',
'my search',
null,
null,
null
);

View File

@ -37,7 +37,7 @@ class AdminSettingsTest extends TestCase {
}
public function testGetForm() {
$this->serviceMock->getParameter('initialStateService')->expects($this->exactly(12))
$this->serviceMock->getParameter('initialStateService')->expects($this->exactly(13))
->method('provideInitialState')
->withConsecutive(
[
@ -55,6 +55,11 @@ class AdminSettingsTest extends TestCase {
'allow_new_mail_accounts',
$this->anything()
],
[
Application::APP_ID,
'layout_message_view',
$this->anything()
],
[
Application::APP_ID,
'llm_processing',