Child folders followup (#15526)
This commit is contained in:
+11
@@ -91,6 +91,17 @@ export class MessageFolderWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
})
|
||||
isSynced: boolean;
|
||||
|
||||
@WorkspaceField({
|
||||
standardId: MESSAGE_FOLDER_STANDARD_FIELD_IDS.parentFolderId,
|
||||
type: FieldMetadataType.TEXT,
|
||||
label: msg`Parent Folder ID`,
|
||||
description: msg`Parent Folder ID`,
|
||||
icon: 'IconFolder',
|
||||
defaultValue: null,
|
||||
})
|
||||
@WorkspaceIsNullable()
|
||||
parentFolderId: string | null;
|
||||
|
||||
@WorkspaceField({
|
||||
standardId: MESSAGE_FOLDER_STANDARD_FIELD_IDS.externalId,
|
||||
type: FieldMetadataType.TEXT,
|
||||
|
||||
+19
-1
@@ -7,6 +7,8 @@ import {
|
||||
|
||||
import { OAuth2ClientManagerService } from 'src/modules/connected-account/oauth2-client-manager/services/oauth2-client-manager.service';
|
||||
import { type ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity';
|
||||
import { extractGmailFolderName } from 'src/modules/messaging/message-folder-manager/drivers/gmail/utils/extract-gmail-folder-name.util';
|
||||
import { getGmailFolderParentId } from 'src/modules/messaging/message-folder-manager/drivers/gmail/utils/get-gmail-folder-parent-id.util';
|
||||
import { MESSAGING_GMAIL_DEFAULT_NOT_SYNCED_LABELS } from 'src/modules/messaging/message-import-manager/drivers/gmail/constants/messaging-gmail-default-not-synced-labels';
|
||||
import { GmailMessageListFetchErrorHandler } from 'src/modules/messaging/message-import-manager/drivers/gmail/services/gmail-message-list-fetch-error-handler.service';
|
||||
|
||||
@@ -53,18 +55,34 @@ export class GmailGetAllFoldersService implements MessageFolderDriver {
|
||||
|
||||
const folders: MessageFolder[] = [];
|
||||
|
||||
const labelNameToIdMap = new Map<string, string>();
|
||||
|
||||
for (const label of labels) {
|
||||
if (!label.name || !label.id) {
|
||||
continue;
|
||||
}
|
||||
|
||||
labelNameToIdMap.set(label.name, label.id);
|
||||
}
|
||||
|
||||
for (const label of labels) {
|
||||
if (!label.name || !label.id) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const isSentFolder = label.id === 'SENT';
|
||||
const folderName = extractGmailFolderName(label.name);
|
||||
const parentFolderId = getGmailFolderParentId(
|
||||
label.name,
|
||||
labelNameToIdMap,
|
||||
);
|
||||
|
||||
folders.push({
|
||||
externalId: label.id,
|
||||
name: label.name,
|
||||
name: folderName,
|
||||
isSynced: this.isSyncedByDefault(label.id),
|
||||
isSentFolder,
|
||||
parentFolderId,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
import { extractGmailFolderName } from 'src/modules/messaging/message-folder-manager/drivers/gmail/utils/extract-gmail-folder-name.util';
|
||||
|
||||
describe('extractGmailFolderName', () => {
|
||||
it('should return full name for top-level folders', () => {
|
||||
expect(extractGmailFolderName('Inbox')).toBe('Inbox');
|
||||
expect(extractGmailFolderName('Sent')).toBe('Sent');
|
||||
});
|
||||
|
||||
it('should extract folder name from nested folder', () => {
|
||||
expect(extractGmailFolderName('Work/Projects')).toBe('Projects');
|
||||
});
|
||||
|
||||
it('should extract folder name from deeply nested folder', () => {
|
||||
expect(extractGmailFolderName('Work/Projects/2024')).toBe('2024');
|
||||
});
|
||||
|
||||
it('should handle Gmail-style nested labels', () => {
|
||||
expect(extractGmailFolderName('[Gmail]/Sent Mail')).toBe('Sent Mail');
|
||||
});
|
||||
|
||||
it('should handle single character names', () => {
|
||||
expect(extractGmailFolderName('A/B/C')).toBe('C');
|
||||
});
|
||||
|
||||
it('should handle special characters', () => {
|
||||
expect(extractGmailFolderName('Work/Client - ABC Corp')).toBe(
|
||||
'Client - ABC Corp',
|
||||
);
|
||||
});
|
||||
});
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
import { getGmailFolderParentId } from 'src/modules/messaging/message-folder-manager/drivers/gmail/utils/get-gmail-folder-parent-id.util';
|
||||
|
||||
describe('getGmailFolderParentId', () => {
|
||||
it('should return null for top-level folders without slash', () => {
|
||||
const labelNameToIdMap = new Map<string, string>([
|
||||
['Inbox', 'INBOX'],
|
||||
['Sent', 'SENT'],
|
||||
]);
|
||||
|
||||
expect(getGmailFolderParentId('Inbox', labelNameToIdMap)).toBeNull();
|
||||
});
|
||||
|
||||
it('should return parent ID for nested folder', () => {
|
||||
const labelNameToIdMap = new Map<string, string>([
|
||||
['Work', 'work-id'],
|
||||
['Work/Projects', 'projects-id'],
|
||||
]);
|
||||
|
||||
expect(getGmailFolderParentId('Work/Projects', labelNameToIdMap)).toBe(
|
||||
'work-id',
|
||||
);
|
||||
});
|
||||
|
||||
it('should return parent ID for deeply nested folder', () => {
|
||||
const labelNameToIdMap = new Map<string, string>([
|
||||
['Work', 'work-id'],
|
||||
['Work/Projects', 'projects-id'],
|
||||
['Work/Projects/2024', '2024-id'],
|
||||
]);
|
||||
|
||||
expect(getGmailFolderParentId('Work/Projects/2024', labelNameToIdMap)).toBe(
|
||||
'projects-id',
|
||||
);
|
||||
});
|
||||
|
||||
it('should return null if parent folder does not exist in map', () => {
|
||||
const labelNameToIdMap = new Map<string, string>([
|
||||
['Work/Projects', 'projects-id'],
|
||||
]);
|
||||
|
||||
expect(
|
||||
getGmailFolderParentId('Work/Projects', labelNameToIdMap),
|
||||
).toBeNull();
|
||||
});
|
||||
|
||||
it('should handle Gmail-style nested labels', () => {
|
||||
const labelNameToIdMap = new Map<string, string>([
|
||||
['[Gmail]', 'gmail-id'],
|
||||
['[Gmail]/Sent Mail', 'sent-id'],
|
||||
]);
|
||||
|
||||
expect(getGmailFolderParentId('[Gmail]/Sent Mail', labelNameToIdMap)).toBe(
|
||||
'gmail-id',
|
||||
);
|
||||
});
|
||||
});
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
export const extractGmailFolderName = (labelName: string): string => {
|
||||
if (!labelName.includes('/')) {
|
||||
return labelName;
|
||||
}
|
||||
|
||||
return labelName.substring(labelName.lastIndexOf('/') + 1);
|
||||
};
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
export const getGmailFolderParentId = (
|
||||
labelName: string,
|
||||
labelNameToIdMap: Map<string, string>,
|
||||
): string | null => {
|
||||
if (!labelName.includes('/')) {
|
||||
return null;
|
||||
}
|
||||
const parentName = labelName.substring(0, labelName.lastIndexOf('/'));
|
||||
|
||||
return labelNameToIdMap.get(parentName) || null;
|
||||
};
|
||||
+53
-30
@@ -55,47 +55,64 @@ export class ImapGetAllFoldersService implements MessageFolderDriver {
|
||||
mailboxList: ListResponse[],
|
||||
): Promise<MessageFolder[]> {
|
||||
const folders: MessageFolder[] = [];
|
||||
const sentFolderPath =
|
||||
const pathToExternalIdMap = new Map<string, string>();
|
||||
const sentFolder =
|
||||
await this.imapFindSentFolderService.findSentFolder(client);
|
||||
|
||||
if (isDefined(sentFolderPath)) {
|
||||
const sentMailbox = mailboxList.find((m) => m.path === sentFolderPath);
|
||||
if (isDefined(sentFolder)) {
|
||||
const sentMailbox = mailboxList.find((m) => m.path === sentFolder.path);
|
||||
const uidValidity = sentMailbox
|
||||
? await this.getUidValidity(client, sentMailbox)
|
||||
: null;
|
||||
|
||||
const externalId = uidValidity
|
||||
? `${sentFolder.path}:${uidValidity.toString()}`
|
||||
: sentFolder.path;
|
||||
|
||||
pathToExternalIdMap.set(sentFolder.path, externalId);
|
||||
|
||||
folders.push({
|
||||
externalId: uidValidity
|
||||
? `${sentFolderPath}:${uidValidity.toString()}`
|
||||
: sentFolderPath,
|
||||
name: sentFolderPath,
|
||||
externalId,
|
||||
name: sentFolder.name,
|
||||
isSynced: true,
|
||||
isSentFolder: true,
|
||||
parentFolderId: sentMailbox?.parentPath || null,
|
||||
});
|
||||
}
|
||||
|
||||
const validMailboxes = mailboxList.filter((mailbox) =>
|
||||
this.isValidMailbox(mailbox, folders),
|
||||
);
|
||||
|
||||
for (const mailbox of validMailboxes) {
|
||||
const isInbox = await this.isInboxFolder(mailbox);
|
||||
for (const mailbox of mailboxList) {
|
||||
const uidValidity = await this.getUidValidity(client, mailbox);
|
||||
const standardFolder = getStandardFolderByRegex(mailbox.path);
|
||||
const isSynced = this.shouldSyncByDefault(
|
||||
mailbox,
|
||||
standardFolder,
|
||||
isInbox,
|
||||
);
|
||||
const externalId = uidValidity
|
||||
? `${mailbox.path}:${uidValidity}`
|
||||
: mailbox.path;
|
||||
|
||||
folders.push({
|
||||
externalId: uidValidity
|
||||
? `${mailbox.path}:${uidValidity}`
|
||||
: mailbox.path,
|
||||
name: mailbox.path,
|
||||
isSynced,
|
||||
isSentFolder: false,
|
||||
});
|
||||
pathToExternalIdMap.set(mailbox.path, externalId);
|
||||
|
||||
if (this.isValidMailbox(mailbox, folders)) {
|
||||
const isInbox = await this.isInboxFolder(mailbox);
|
||||
const standardFolder = getStandardFolderByRegex(mailbox.path);
|
||||
const isSynced = this.shouldSyncByDefault(
|
||||
mailbox,
|
||||
standardFolder,
|
||||
isInbox,
|
||||
);
|
||||
|
||||
folders.push({
|
||||
externalId,
|
||||
name: mailbox.name,
|
||||
isSynced,
|
||||
isSentFolder: false,
|
||||
parentFolderId: mailbox.parentPath || null,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
for (const folder of folders) {
|
||||
if (folder.parentFolderId) {
|
||||
const parentExternalId = pathToExternalIdMap.get(folder.parentFolderId);
|
||||
|
||||
folder.parentFolderId = parentExternalId || null;
|
||||
}
|
||||
}
|
||||
|
||||
return folders;
|
||||
@@ -109,9 +126,15 @@ export class ImapGetAllFoldersService implements MessageFolderDriver {
|
||||
return false;
|
||||
}
|
||||
|
||||
const isDuplicate = existingFolders.some(
|
||||
(folder) => folder.name === mailbox.path,
|
||||
);
|
||||
const isDuplicate = existingFolders.some((folder) => {
|
||||
const folderPath = folder?.externalId?.split(':')[0];
|
||||
|
||||
if (!isDefined(folderPath)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return folderPath === mailbox.path;
|
||||
});
|
||||
|
||||
return !isDuplicate;
|
||||
}
|
||||
|
||||
+64
-2
@@ -1,5 +1,7 @@
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import {
|
||||
MessageFolder,
|
||||
MessageFolderDriver,
|
||||
@@ -9,13 +11,13 @@ import { OAuth2ClientManagerService } from 'src/modules/connected-account/oauth2
|
||||
import { type ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity';
|
||||
import { MicrosoftMessageListFetchErrorHandler } from 'src/modules/messaging/message-import-manager/drivers/microsoft/services/microsoft-message-list-fetch-error-handler.service';
|
||||
import { StandardFolder } from 'src/modules/messaging/message-import-manager/drivers/types/standard-folder';
|
||||
import { getStandardFolderByRegex } from 'src/modules/messaging/message-import-manager/drivers/utils/get-standard-folder-by-regex';
|
||||
|
||||
type MicrosoftGraphFolder = {
|
||||
id: string;
|
||||
displayName: string;
|
||||
childFolderCount?: number;
|
||||
parentFolderId?: string;
|
||||
wellKnownName?: string;
|
||||
};
|
||||
|
||||
const MESSAGING_MICROSOFT_MAIL_FOLDERS_LIST_MAX_RESULT = 999;
|
||||
@@ -56,6 +58,7 @@ export class MicrosoftGetAllFoldersService implements MessageFolderDriver {
|
||||
});
|
||||
|
||||
const folders = (response.value as MicrosoftGraphFolder[]) || [];
|
||||
const rootFolderId = this.getRootFolderId(folders);
|
||||
const folderInfos: MessageFolder[] = [];
|
||||
|
||||
for (const folder of folders) {
|
||||
@@ -63,7 +66,9 @@ export class MicrosoftGetAllFoldersService implements MessageFolderDriver {
|
||||
continue;
|
||||
}
|
||||
|
||||
const standardFolder = getStandardFolderByRegex(folder.displayName);
|
||||
const standardFolder = this.getStandardFolderFromWellKnownName(
|
||||
folder.wellKnownName,
|
||||
);
|
||||
const isSentFolder = this.isSentFolder(standardFolder);
|
||||
const isSynced = this.shouldSyncByDefault(standardFolder);
|
||||
|
||||
@@ -72,6 +77,10 @@ export class MicrosoftGetAllFoldersService implements MessageFolderDriver {
|
||||
name: folder.displayName,
|
||||
isSynced,
|
||||
isSentFolder,
|
||||
parentFolderId: this.getParentFolderId(
|
||||
folder.parentFolderId,
|
||||
rootFolderId,
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -105,4 +114,57 @@ export class MicrosoftGetAllFoldersService implements MessageFolderDriver {
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private getStandardFolderFromWellKnownName(
|
||||
wellKnownName?: string,
|
||||
): StandardFolder | null {
|
||||
if (!isDefined(wellKnownName)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
switch (wellKnownName.toLowerCase()) {
|
||||
case 'inbox':
|
||||
return StandardFolder.INBOX;
|
||||
case 'drafts':
|
||||
return StandardFolder.DRAFTS;
|
||||
case 'sentitems':
|
||||
return StandardFolder.SENT;
|
||||
case 'deleteditems':
|
||||
return StandardFolder.TRASH;
|
||||
case 'junkemail':
|
||||
return StandardFolder.JUNK;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* All Microsoft folders have a parentFolderId including the standard folders
|
||||
* which point to root node which doesn't exits in the API response.
|
||||
* We remove this to simplify the folder hierarchy on frontend.
|
||||
*/
|
||||
private getRootFolderId(folders: MicrosoftGraphFolder[]): string | null {
|
||||
for (const folder of folders) {
|
||||
if (isDefined(folder.wellKnownName) && isDefined(folder.parentFolderId)) {
|
||||
return folder.parentFolderId;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private getParentFolderId(
|
||||
parentFolderId: string | undefined,
|
||||
rootFolderId: string | null,
|
||||
): string | null {
|
||||
if (!isDefined(parentFolderId)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (parentFolderId === rootFolderId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return parentFolderId;
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@ import { type MessageFolderWorkspaceEntity } from 'src/modules/messaging/common/
|
||||
|
||||
export type MessageFolder = Pick<
|
||||
MessageFolderWorkspaceEntity,
|
||||
'name' | 'isSynced' | 'isSentFolder' | 'externalId'
|
||||
'name' | 'isSynced' | 'isSentFolder' | 'externalId' | 'parentFolderId'
|
||||
>;
|
||||
|
||||
export interface MessageFolderDriver {
|
||||
|
||||
+7
-1
@@ -32,10 +32,14 @@ type MessageFolderToInsert = Pick<
|
||||
| 'isSynced'
|
||||
| 'isSentFolder'
|
||||
| 'externalId'
|
||||
| 'parentFolderId'
|
||||
>;
|
||||
|
||||
type MessageFolderToUpdate = Partial<
|
||||
Pick<MessageFolderWorkspaceEntity, 'name' | 'externalId' | 'isSentFolder'>
|
||||
Pick<
|
||||
MessageFolderWorkspaceEntity,
|
||||
'name' | 'externalId' | 'isSentFolder' | 'parentFolderId'
|
||||
>
|
||||
>;
|
||||
|
||||
@Injectable()
|
||||
@@ -114,6 +118,7 @@ export class SyncMessageFoldersService {
|
||||
name: folder.name,
|
||||
externalId: folder.externalId,
|
||||
isSentFolder: folder.isSentFolder,
|
||||
parentFolderId: folder.parentFolderId,
|
||||
},
|
||||
]);
|
||||
continue;
|
||||
@@ -127,6 +132,7 @@ export class SyncMessageFoldersService {
|
||||
isSynced: folder.isSynced,
|
||||
isSentFolder: folder.isSentFolder,
|
||||
externalId: folder.externalId,
|
||||
parentFolderId: folder.parentFolderId,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
+30
-9
@@ -5,6 +5,11 @@ import { ListResponse, type ImapFlow } from 'imapflow';
|
||||
|
||||
import { getImapSentFolderCandidatesByRegex } from 'src/modules/messaging/message-import-manager/drivers/imap/utils/get-sent-folder-candidates-by-regex.util';
|
||||
|
||||
type SentFolderResult = {
|
||||
name: string;
|
||||
path: string;
|
||||
} | null;
|
||||
|
||||
/**
|
||||
* Service to find sent folder using IMAP special-use flags
|
||||
*
|
||||
@@ -19,7 +24,7 @@ import { getImapSentFolderCandidatesByRegex } from 'src/modules/messaging/messag
|
||||
export class ImapFindSentFolderService {
|
||||
private readonly logger = new Logger(ImapFindSentFolderService.name);
|
||||
|
||||
public async findSentFolder(client: ImapFlow): Promise<string | null> {
|
||||
public async findSentFolder(client: ImapFlow): Promise<SentFolderResult> {
|
||||
try {
|
||||
const list = await client.list();
|
||||
|
||||
@@ -60,7 +65,7 @@ export class ImapFindSentFolderService {
|
||||
private async findSentFolderBySpecialUse(
|
||||
client: ImapFlow,
|
||||
list: ListResponse[],
|
||||
): Promise<string | null> {
|
||||
): Promise<SentFolderResult> {
|
||||
for (const folder of list) {
|
||||
if (folder.specialUse && folder.specialUse.includes('\\Sent')) {
|
||||
this.logger.log(
|
||||
@@ -73,7 +78,10 @@ export class ImapFindSentFolderService {
|
||||
);
|
||||
|
||||
if (messageCount > 0) {
|
||||
return folder.path;
|
||||
return {
|
||||
name: folder.name,
|
||||
path: folder.path,
|
||||
};
|
||||
}
|
||||
|
||||
this.logger.warn(
|
||||
@@ -90,25 +98,38 @@ export class ImapFindSentFolderService {
|
||||
private async findSentFolderByRegexCandidates(
|
||||
client: ImapFlow,
|
||||
list: ListResponse[],
|
||||
): Promise<string | null> {
|
||||
): Promise<SentFolderResult> {
|
||||
const regexCandidateFolders = getImapSentFolderCandidatesByRegex(list);
|
||||
|
||||
for (const folder of regexCandidateFolders) {
|
||||
const messageCount = await this.getFolderMessageCount(client, folder);
|
||||
const messageCount = await this.getFolderMessageCount(
|
||||
client,
|
||||
folder.path,
|
||||
);
|
||||
|
||||
if (messageCount > 0) {
|
||||
this.logger.log(`Selected sent folder via pattern match: ${folder}`);
|
||||
this.logger.log(
|
||||
`Selected sent folder via pattern match: ${folder.path}`,
|
||||
);
|
||||
|
||||
return folder;
|
||||
return {
|
||||
name: folder.name,
|
||||
path: folder.path,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (regexCandidateFolders.length > 0) {
|
||||
this.logger.log(
|
||||
`Using first regex candidate sent folder: ${regexCandidateFolders[0]} (no messages found in any regex candidate)`,
|
||||
`Using first regex candidate sent folder: ${regexCandidateFolders[0].path} (no messages found in any regex candidate)`,
|
||||
);
|
||||
|
||||
return regexCandidateFolders[0];
|
||||
const folder = regexCandidateFolders[0];
|
||||
|
||||
return {
|
||||
name: folder.name,
|
||||
path: folder.path,
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
+8
-1
@@ -41,10 +41,17 @@ export class ImapGetMessageListService {
|
||||
for (const folder of messageFolders) {
|
||||
this.logger.log(`Processing folder: ${folder.name}`);
|
||||
|
||||
const folderPath = folder.externalId?.split(':')[0];
|
||||
|
||||
if (!folderPath) {
|
||||
this.logger.warn(`Folder ${folder.name} has no path. Skipping.`);
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await this.getMessageList(
|
||||
client,
|
||||
folder.name,
|
||||
folderPath,
|
||||
folder,
|
||||
);
|
||||
|
||||
|
||||
+32
-12
@@ -3,7 +3,7 @@ import { type ListResponse } from 'imapflow';
|
||||
import { getImapSentFolderCandidatesByRegex } from 'src/modules/messaging/message-import-manager/drivers/imap/utils/get-sent-folder-candidates-by-regex.util';
|
||||
|
||||
function makeList(paths: string[]): ListResponse[] {
|
||||
return paths.map((p) => ({ path: p }) as ListResponse);
|
||||
return paths.map((p) => ({ path: p, name: p }) as ListResponse);
|
||||
}
|
||||
|
||||
describe('getSentFolderCandidatesByRegex', () => {
|
||||
@@ -18,7 +18,9 @@ describe('getSentFolderCandidatesByRegex', () => {
|
||||
const input = makeList(englishVariants);
|
||||
const result = getImapSentFolderCandidatesByRegex(input);
|
||||
|
||||
expect(result).toEqual(expect.arrayContaining(englishVariants));
|
||||
expect(result.map((r) => r.path)).toEqual(
|
||||
expect.arrayContaining(englishVariants),
|
||||
);
|
||||
});
|
||||
|
||||
it('matches French variants', () => {
|
||||
@@ -26,7 +28,9 @@ describe('getSentFolderCandidatesByRegex', () => {
|
||||
const input = makeList(frenchVariants);
|
||||
const result = getImapSentFolderCandidatesByRegex(input);
|
||||
|
||||
expect(result).toEqual(expect.arrayContaining(frenchVariants));
|
||||
expect(result.map((r) => r.path)).toEqual(
|
||||
expect.arrayContaining(frenchVariants),
|
||||
);
|
||||
});
|
||||
|
||||
it('matches German variants', () => {
|
||||
@@ -34,7 +38,9 @@ describe('getSentFolderCandidatesByRegex', () => {
|
||||
const input = makeList(germanVariants);
|
||||
const result = getImapSentFolderCandidatesByRegex(input);
|
||||
|
||||
expect(result).toEqual(expect.arrayContaining(germanVariants));
|
||||
expect(result.map((r) => r.path)).toEqual(
|
||||
expect.arrayContaining(germanVariants),
|
||||
);
|
||||
});
|
||||
|
||||
it('matches Spanish variants', () => {
|
||||
@@ -42,7 +48,9 @@ describe('getSentFolderCandidatesByRegex', () => {
|
||||
const input = makeList(spanishVariants);
|
||||
const result = getImapSentFolderCandidatesByRegex(input);
|
||||
|
||||
expect(result).toEqual(expect.arrayContaining(spanishVariants));
|
||||
expect(result.map((r) => r.path)).toEqual(
|
||||
expect.arrayContaining(spanishVariants),
|
||||
);
|
||||
});
|
||||
|
||||
it('matches Portuguese variants', () => {
|
||||
@@ -50,7 +58,9 @@ describe('getSentFolderCandidatesByRegex', () => {
|
||||
const input = makeList(portugueseVariants);
|
||||
const result = getImapSentFolderCandidatesByRegex(input);
|
||||
|
||||
expect(result).toEqual(expect.arrayContaining(portugueseVariants));
|
||||
expect(result.map((r) => r.path)).toEqual(
|
||||
expect.arrayContaining(portugueseVariants),
|
||||
);
|
||||
});
|
||||
|
||||
it('matches Italian variants', () => {
|
||||
@@ -58,7 +68,9 @@ describe('getSentFolderCandidatesByRegex', () => {
|
||||
const input = makeList(italianVariants);
|
||||
const result = getImapSentFolderCandidatesByRegex(input);
|
||||
|
||||
expect(result).toEqual(expect.arrayContaining(italianVariants));
|
||||
expect(result.map((r) => r.path)).toEqual(
|
||||
expect.arrayContaining(italianVariants),
|
||||
);
|
||||
});
|
||||
|
||||
it('matches Korean variant', () => {
|
||||
@@ -66,7 +78,7 @@ describe('getSentFolderCandidatesByRegex', () => {
|
||||
const input = makeList(koreanVariants);
|
||||
const result = getImapSentFolderCandidatesByRegex(input);
|
||||
|
||||
expect(result).toEqual(koreanVariants);
|
||||
expect(result.map((r) => r.path)).toEqual(koreanVariants);
|
||||
});
|
||||
|
||||
it('matches Japanese variants', () => {
|
||||
@@ -74,7 +86,9 @@ describe('getSentFolderCandidatesByRegex', () => {
|
||||
const input = makeList(japaneseVariants);
|
||||
const result = getImapSentFolderCandidatesByRegex(input);
|
||||
|
||||
expect(result).toEqual(expect.arrayContaining(japaneseVariants));
|
||||
expect(result.map((r) => r.path)).toEqual(
|
||||
expect.arrayContaining(japaneseVariants),
|
||||
);
|
||||
});
|
||||
|
||||
it('matches Polish variants', () => {
|
||||
@@ -82,7 +96,9 @@ describe('getSentFolderCandidatesByRegex', () => {
|
||||
const input = makeList(polishVariants);
|
||||
const result = getImapSentFolderCandidatesByRegex(input);
|
||||
|
||||
expect(result).toEqual(expect.arrayContaining(polishVariants));
|
||||
expect(result.map((r) => r.path)).toEqual(
|
||||
expect.arrayContaining(polishVariants),
|
||||
);
|
||||
});
|
||||
|
||||
it('matches Russian variants', () => {
|
||||
@@ -95,7 +111,9 @@ describe('getSentFolderCandidatesByRegex', () => {
|
||||
const input = makeList(russianVariants);
|
||||
const result = getImapSentFolderCandidatesByRegex(input);
|
||||
|
||||
expect(result).toEqual(expect.arrayContaining(russianVariants));
|
||||
expect(result.map((r) => r.path)).toEqual(
|
||||
expect.arrayContaining(russianVariants),
|
||||
);
|
||||
});
|
||||
|
||||
it('matches Gmail special folder', () => {
|
||||
@@ -103,7 +121,9 @@ describe('getSentFolderCandidatesByRegex', () => {
|
||||
const input = makeList(gmailVariants);
|
||||
const result = getImapSentFolderCandidatesByRegex(input);
|
||||
|
||||
expect(result).toEqual(expect.arrayContaining(gmailVariants));
|
||||
expect(result.map((r) => r.path)).toEqual(
|
||||
expect.arrayContaining(gmailVariants),
|
||||
);
|
||||
});
|
||||
|
||||
it('does not match unrelated folders', () => {
|
||||
|
||||
+9
-2
@@ -5,7 +5,7 @@ import { getStandardFolderByRegex } from 'src/modules/messaging/message-import-m
|
||||
|
||||
export function getImapSentFolderCandidatesByRegex(
|
||||
list: ListResponse[],
|
||||
): string[] {
|
||||
): { name: string; path: string }[] {
|
||||
const regexCandidateFolders: string[] = [];
|
||||
|
||||
for (const folder of list) {
|
||||
@@ -16,5 +16,12 @@ export function getImapSentFolderCandidatesByRegex(
|
||||
}
|
||||
}
|
||||
|
||||
return regexCandidateFolders;
|
||||
return regexCandidateFolders.map((folderPath) => {
|
||||
const folder = list.find((folder) => folder.path === folderPath);
|
||||
|
||||
return {
|
||||
name: folder?.name ?? folderPath,
|
||||
path: folderPath,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user